diff --git a/.cfignore b/.cfignore new file mode 100644 index 0000000..638c810 --- /dev/null +++ b/.cfignore @@ -0,0 +1,2 @@ +backend/node_modules +backend/template-env.js \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c4de463 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true + +# js, jsx, json +[*.{js,jsx,json}] +charset = utf-8 +indent_style = space +indent_size = 2 \ No newline at end of file diff --git a/.github/actions/cf-action/Dockerfile b/.github/actions/cf-action/Dockerfile new file mode 100644 index 0000000..063b646 --- /dev/null +++ b/.github/actions/cf-action/Dockerfile @@ -0,0 +1,10 @@ +FROM ubuntu:18.04 + +WORKDIR / +RUN wget -q -O - https://packages.cloudfoundry.org/debian/cli.cloudfoundry.org.key | apt-key add - +RUN echo "deb https://packages.cloudfoundry.org/debian stable main" | tee /etc/apt/sources.list.d/cloudfoundry-cli.list +RUN apt-get install -y cf7-cli + + +ADD entrypoint.sh /entrypoint.sh +ENTRYPOINT ["/entrypoint.sh"] \ No newline at end of file diff --git a/.github/actions/cf-action/LICENSE b/.github/actions/cf-action/LICENSE new file mode 100644 index 0000000..0658945 --- /dev/null +++ b/.github/actions/cf-action/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2020 James Hunt + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software.. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. diff --git a/.github/actions/cf-action/action.yml b/.github/actions/cf-action/action.yml new file mode 100644 index 0000000..907e7b7 --- /dev/null +++ b/.github/actions/cf-action/action.yml @@ -0,0 +1,29 @@ +name: "CF Push" +description: Push an application to a Cloud Foundry +inputs: + manifest: + description: The path to your CF application manifest + required: true + api: + description: The Cloud Foundry API endpoint + required: true + username: + description: Your Cloud Foundry authentication username (or email address) + required: true + password: + description: Your Cloud Foundry authentication password + required: true + org: + description: The name of the Cloud Foundry organization in which to deploy + required: true + space: + description: The name of the Cloud Foundry space in which to deploy + required: true + validate: + description: Whether or not to validate the CF API's TLS certificate + required: false + default: true + +runs: + using: docker + image: Dockerfile diff --git a/.github/actions/cf-action/entrypoint.sh b/.github/actions/cf-action/entrypoint.sh new file mode 100755 index 0000000..f230de3 --- /dev/null +++ b/.github/actions/cf-action/entrypoint.sh @@ -0,0 +1,10 @@ +#!/bin/sh +set -eu +cf_opts= +if [ "x${INPUT_VALIDATE}" = "xfalse" ]; then + cf_opts="--skip-ssl-validation" +fi +cf api ${INPUT_API} ${cf_opts} +CF_USERNAME=${INPUT_USERNAME} CF_PASSWORD=${INPUT_PASSWORD} cf auth +cf target -o ${INPUT_ORG} -s ${INPUT_SPACE} +cf push ${INPUT_APP} -f ${INPUT_MANIFEST} diff --git a/.github/workflows/push-staging.yml b/.github/workflows/push-staging.yml new file mode 100644 index 0000000..e69de29 diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..da6bae1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +.env +.vscode +.DS_Store + +node_modules/ + +lockbox + +client/ +*.css + +dev* +prod* +stage* + + +combined.log +error.log +audit.log + +coverage +scratch + +# keys +lockbox/keys.enc +lockbox/prod +lockbox/staging + +_* +# Actual data + +api/coverage +api/tmp +api/logs \ No newline at end of file diff --git a/api/.dockerignore b/api/.dockerignore new file mode 100644 index 0000000..5dd946f --- /dev/null +++ b/api/.dockerignore @@ -0,0 +1,6 @@ +node_modules/ + +*.md + +Dockerfile +Dockerfile.dev \ No newline at end of file diff --git a/api/.eslintignore b/api/.eslintignore new file mode 100644 index 0000000..07ed706 --- /dev/null +++ b/api/.eslintignore @@ -0,0 +1 @@ +build/* \ No newline at end of file diff --git a/api/.eslintrc b/api/.eslintrc new file mode 100644 index 0000000..e0760e7 --- /dev/null +++ b/api/.eslintrc @@ -0,0 +1,24 @@ +{ + "parser": "@typescript-eslint/parser", + "extends": ["plugin:@typescript-eslint/recommended", "prettier/@typescript-eslint", "plugin:prettier/recommended"], + "parserOptions": { + "ecmaVersion": 2018, + "sourceType": "module" + }, + "rules": { + "@typescript-eslint/explicit-member-accessibility": 0, + "@typescript-eslint/explicit-function-return-type": 0, + "@typescript-eslint/no-parameter-properties": 0, + "@typescript-eslint/interface-name-prefix": 0, + "@typescript-eslint/explicit-module-boundary-types": 0, + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/ban-ts-comment" : "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/no-namespace": "off" + }, + "env" : { + "commonjs": true, + "es6": true, + "node": true + } + } \ No newline at end of file diff --git a/api/.gitignore b/api/.gitignore new file mode 100644 index 0000000..eca9c16 --- /dev/null +++ b/api/.gitignore @@ -0,0 +1,46 @@ +lib-cov +*.seed +*.log +*.csv +*.dat +*.out +*.pid +*.gz +*.swp +*.error.log + + +pids +logs/*.log +logs +results +tmp + +# Build +public/css/main.css + +# Coverage reports +coverage + +# API keys and secrets +.env + +# Dependency directory +node_modules +bower_components + +# Editors +.idea +*.iml + +# OS metadata +.DS_Store +Thumbs.db + +# Ignore built ts files +dist/**/* + +# ignore yarn.lock +yarn.lock + +build \ No newline at end of file diff --git a/api/.prettierrc b/api/.prettierrc new file mode 100644 index 0000000..6b4d840 --- /dev/null +++ b/api/.prettierrc @@ -0,0 +1,9 @@ + +{ + "printWidth": 150, + "tabWidth": 2, + "singleQuote": true, + "trailingComma": "all", + "semi": true, + "arrowParens": "avoid" +} \ No newline at end of file diff --git a/api/Dockerfile b/api/Dockerfile new file mode 100644 index 0000000..bd28822 --- /dev/null +++ b/api/Dockerfile @@ -0,0 +1,15 @@ +FROM node:12.16.2-alpine + +WORKDIR /app + +COPY package.json . +COPY package-lock.json . + +RUN npm install + +COPY . ./app + +ENV PORT=9000 +EXPOSE 9000 + +CMD ["npm", "start"] \ No newline at end of file diff --git a/api/Dockerfile.compose b/api/Dockerfile.compose new file mode 100644 index 0000000..f928560 --- /dev/null +++ b/api/Dockerfile.compose @@ -0,0 +1,14 @@ +FROM node:14.17.3-alpine +WORKDIR /opt/node_app/app + +COPY package.json package-lock.json ./ +COPY tsconfig.json ./ + +RUN npm ci --no-optional + +COPY . . + +ENV PORT=9009 +EXPOSE 9009 + +CMD ["npm", "run", "dev"] \ No newline at end of file diff --git a/api/Dockerfile.dev b/api/Dockerfile.dev new file mode 100644 index 0000000..abe15ca --- /dev/null +++ b/api/Dockerfile.dev @@ -0,0 +1,17 @@ +FROM node:12.16.2-alpine as builder + +COPY package.json package-lock.json ./ +COPY tsconfig.json ./ + +RUN npm ci --no-optional && mkdir /api-build && mv ./node_modules ./api-build +WORKDIR /api-build + +COPY . . + +RUN npm run build +RUN rm -rf src/ + +ENV PORT=9009 +EXPOSE 9009 + +CMD ["npm", "run", "start"] \ No newline at end of file diff --git a/api/buildVersion.txt b/api/buildVersion.txt new file mode 100644 index 0000000..e6c3369 --- /dev/null +++ b/api/buildVersion.txt @@ -0,0 +1 @@ +"3.1.0" diff --git a/api/jest.config.js b/api/jest.config.js new file mode 100644 index 0000000..bd816e9 --- /dev/null +++ b/api/jest.config.js @@ -0,0 +1,8 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + testPathIgnorePatterns: ['/build/', '/node_modules/', '/tests/integration.tests'], + collectCoverageFrom: ['**/*.{ts,tsx}', '!**/node_modules/**', '!**/vendor/**'], + collectCoverage: false, + verbose: true, +}; diff --git a/api/nodemon.json b/api/nodemon.json new file mode 100644 index 0000000..6cee551 --- /dev/null +++ b/api/nodemon.json @@ -0,0 +1,13 @@ + +{ + "watch": [ + "src", + ".env" + ], + "ext": "js,ts,json", + "ignore": [ + "src/**/*.spec.ts", + "src/**/*.test.ts" + ], + "exec": "ts-node --transpile-only src/server.ts" +} \ No newline at end of file diff --git a/api/package-lock.json b/api/package-lock.json new file mode 100644 index 0000000..dcb09f1 --- /dev/null +++ b/api/package-lock.json @@ -0,0 +1,15618 @@ +{ + "name": "smeqa-art", + "version": "4.1.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "smeqa-art", + "version": "4.1.0", + "license": "ISC", + "dependencies": { + "class-transformer": "^0.3.1", + "class-validator": "^0.12.2", + "compression": "^1.7.4", + "connect-pg-simple": "^6.2.1", + "cookie-parser": "^1.4.5", + "cors": "^2.8.5", + "dotenv": "^8.2.0", + "express": "^4.17.1", + "express-session": "^1.17.1", + "fast-csv": "^4.3.6", + "helmet": "^4.2.0", + "hpp": "^0.2.3", + "marked": "^3.0.4", + "morgan": "^1.10.0", + "multer": "^1.4.2", + "node-jose": "^2.0.0", + "openid-client": "^4.7.4", + "passport": "^0.4.1", + "passport-custom": "^1.1.1", + "pg": "^8.5.1", + "sequelize": "^6.5.0", + "uuid": "^8.3.2", + "winston": "^3.3.3", + "winston-daily-rotate-file": "^4.5.0" + }, + "devDependencies": { + "@types/compression": "^1.7.0", + "@types/connect-pg-simple": "^4.2.2", + "@types/cookie-parser": "^1.4.2", + "@types/cors": "^2.8.9", + "@types/express": "^4.17.9", + "@types/express-session": "^1.17.3", + "@types/hpp": "^0.2.1", + "@types/jest": "^27.0.2", + "@types/marked": "^3.0.1", + "@types/morgan": "^1.9.2", + "@types/multer": "^1.4.5", + "@types/node": "^14.14.14", + "@types/node-jose": "^1.1.5", + "@types/passport": "^1.0.4", + "@types/pg": "7.4.8", + "@types/sequelize": "^4.28.9", + "@types/showdown": "^1.9.4", + "@types/supertest": "^2.0.10", + "@types/uuid": "^8.3.0", + "@types/validator": "^13.1.1", + "@typescript-eslint/eslint-plugin": "^4.10.0", + "@typescript-eslint/parser": "^4.10.0", + "eslint": "^7.15.0", + "eslint-config-prettier": "^7.0.0", + "eslint-plugin-prettier": "^3.3.0", + "jest": "^27.0.6", + "nodemon": "^2.0.6", + "prettier": "^2.2.1", + "sequelize-mock": "^0.10.2", + "supertest": "^6.0.1", + "ts-jest": "^27.0.3", + "ts-node": "^10.1.0", + "typescript": "^4.1.3" + }, + "engines": { + "node": "14.18.1" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.15.0.tgz", + "integrity": "sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.15.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.15.5.tgz", + "integrity": "sha512-pYgXxiwAgQpgM1bNkZsDEq85f0ggXMA5L7c+o3tskGMh2BunCI9QUwB9Z4jpvXUOuMdyGKiGKQiRe11VS6Jzvg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.15.4", + "@babel/helper-compilation-targets": "^7.15.4", + "@babel/helper-module-transforms": "^7.15.4", + "@babel/helpers": "^7.15.4", + "@babel/parser": "^7.15.5", + "@babel/template": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.4", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.1.2", + "semver": "^6.3.0", + "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/@babel/code-frame": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/core/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.4.tgz", + "integrity": "sha512-d3itta0tu+UayjEORPNz6e1T3FtvWlP5N4V5M+lhp/CxT4oAA7/NcScnpRyspUMLK6tu9MNHmQHxRykuN2R7hw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.15.4", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.4.tgz", + "integrity": "sha512-rMWPCirulnPSe4d+gwdWXLfAXTTBj8M3guAf5xFQJ0nvFY7tfNAFnWdqaHegHlgDZOCT4qvhF3BYlSJag8yhqQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.15.0", + "@babel/helper-validator-option": "^7.14.5", + "browserslist": "^4.16.6", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz", + "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", + "dev": true, + "dependencies": { + "@babel/helper-get-function-arity": "^7.15.4", + "@babel/template": "^7.15.4", + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-get-function-arity": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz", + "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.15.4.tgz", + "integrity": "sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.4.tgz", + "integrity": "sha512-cokOMkxC/BTyNP1AlY25HuBWM32iCEsLPI4BHDpJCHHm1FU2E7dKWWIXJgQgSFiu4lp8q3bL1BIKwqkSUviqtA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.15.4.tgz", + "integrity": "sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.7.tgz", + "integrity": "sha512-ZNqjjQG/AuFfekFTY+7nY4RgBSklgTu970c7Rj3m/JOhIu5KPBUuTA9AY6zaKcUvk4g6EbDXdBnhi35FAssdSw==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.15.4", + "@babel/helper-replace-supers": "^7.15.4", + "@babel/helper-simple-access": "^7.15.4", + "@babel/helper-split-export-declaration": "^7.15.4", + "@babel/helper-validator-identifier": "^7.15.7", + "@babel/template": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.15.4.tgz", + "integrity": "sha512-E/z9rfbAOt1vDW1DR7k4SzhzotVV5+qMciWV6LaG1g4jeFrkDlJedjtV4h0i4Q/ITnUu+Pk08M7fczsB9GXBDw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", + "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.15.4.tgz", + "integrity": "sha512-/ztT6khaXF37MS47fufrKvIsiQkx1LBRvSJNzRqmbyeZnTwU9qBxXYLaaT/6KaxfKhjs2Wy8kG8ZdsFUuWBjzw==", + "dev": true, + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.15.4", + "@babel/helper-optimise-call-expression": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.15.4.tgz", + "integrity": "sha512-UzazrDoIVOZZcTeHHEPYrr1MvTR/K+wgLg6MY6e1CJyaRhbibftF6fR2KU2sFRtI/nERUZR9fBd6aKgBlIBaPg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz", + "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", + "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.15.4.tgz", + "integrity": "sha512-V45u6dqEJ3w2rlryYYXf6i9rQ5YMNu4FLS6ngs8ikblhu2VdR1AqAd6aJjBzmf2Qzh6KOLqKHxEN9+TFbAkAVQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.7.tgz", + "integrity": "sha512-rycZXvQ+xS9QyIcJ9HXeDWf1uxqlbVFAUq0Rq0dbc50Zb/+wUe/ehyfzGfm9KZZF0kBejYgxltBXocP+gKdL2g==", + "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-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.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.14.5.tgz", + "integrity": "sha512-u6OXzDaIXjEstBRRoBCQ/uKQKlbuaeE5in0RvWdA4pN6AhqxTIwUsnHPU1CFZA/amYObMsuWhYfRl3Ch90HD0Q==", + "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/template": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", + "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.14.5", + "@babel/parser": "^7.15.4", + "@babel/types": "^7.15.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template/node_modules/@babel/code-frame": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.4.tgz", + "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.15.4", + "@babel/helper-function-name": "^7.15.4", + "@babel/helper-hoist-variables": "^7.15.4", + "@babel/helper-split-export-declaration": "^7.15.4", + "@babel/parser": "^7.15.4", + "@babel/types": "^7.15.4", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/@babel/code-frame": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.15.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", + "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.14.9", + "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-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.6.1.tgz", + "integrity": "sha512-DX3Z+T5dt1ockmPdobJS/FAsQPW4V4SrWEhD2iYQT2Cb2tQsiMnYxrcUH9By/Z3B+v0S5LMBkQtV/XOBbpLEOg==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-consumer": "0.8.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.2.tgz", + "integrity": "sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q==", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", + "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/@fast-csv/format": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@fast-csv/format/-/format-4.3.5.tgz", + "integrity": "sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==", + "dependencies": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isboolean": "^3.0.3", + "lodash.isequal": "^4.5.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0" + } + }, + "node_modules/@fast-csv/parse": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@fast-csv/parse/-/parse-4.3.6.tgz", + "integrity": "sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==", + "dependencies": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.groupby": "^4.6.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0", + "lodash.isundefined": "^3.0.1", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", + "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", + "dev": true + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "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/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "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": "27.2.4", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.2.4.tgz", + "integrity": "sha512-94znCKynPZpDpYHQ6esRJSc11AmONrVkBOBZiD7S+bSubHhrUfbS95EY5HIOxhm4PQO7cnvZkL3oJcY0oMA+Wg==", + "dev": true, + "dependencies": { + "@jest/types": "^27.2.4", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^27.2.4", + "jest-util": "^27.2.4", + "slash": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/core": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.2.4.tgz", + "integrity": "sha512-UNQLyy+rXoojNm2MGlapgzWhZD1CT1zcHZQYeiD0xE7MtJfC19Q6J5D/Lm2l7i4V97T30usKDoEtjI8vKwWcLg==", + "dev": true, + "dependencies": { + "@jest/console": "^27.2.4", + "@jest/reporters": "^27.2.4", + "@jest/test-result": "^27.2.4", + "@jest/transform": "^27.2.4", + "@jest/types": "^27.2.4", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.8.1", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "jest-changed-files": "^27.2.4", + "jest-config": "^27.2.4", + "jest-haste-map": "^27.2.4", + "jest-message-util": "^27.2.4", + "jest-regex-util": "^27.0.6", + "jest-resolve": "^27.2.4", + "jest-resolve-dependencies": "^27.2.4", + "jest-runner": "^27.2.4", + "jest-runtime": "^27.2.4", + "jest-snapshot": "^27.2.4", + "jest-util": "^27.2.4", + "jest-validate": "^27.2.4", + "jest-watcher": "^27.2.4", + "micromatch": "^4.0.4", + "rimraf": "^3.0.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.2.4.tgz", + "integrity": "sha512-wkuui5yr3SSQW0XD0Qm3TATUbL/WE3LDEM3ulC+RCQhMf2yxhci8x7svGkZ4ivJ6Pc94oOzpZ6cdHBAMSYd1ew==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^27.2.4", + "@jest/types": "^27.2.4", + "@types/node": "*", + "jest-mock": "^27.2.4" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.2.4.tgz", + "integrity": "sha512-cs/TzvwWUM7kAA6Qm/890SK6JJ2pD5RfDNM3SSEom6BmdyV6OiWP1qf/pqo6ts6xwpcM36oN0wSEzcZWc6/B6w==", + "dev": true, + "dependencies": { + "@jest/types": "^27.2.4", + "@sinonjs/fake-timers": "^8.0.1", + "@types/node": "*", + "jest-message-util": "^27.2.4", + "jest-mock": "^27.2.4", + "jest-util": "^27.2.4" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.2.4.tgz", + "integrity": "sha512-DRsRs5dh0i+fA9mGHylTU19+8fhzNJoEzrgsu+zgJoZth3x8/0juCQ8nVVdW1er4Cqifb/ET7/hACYVPD0dBEA==", + "dev": true, + "dependencies": { + "@jest/environment": "^27.2.4", + "@jest/types": "^27.2.4", + "expect": "^27.2.4" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.2.4.tgz", + "integrity": "sha512-LHeSdDnDZkDnJ8kvnjcqV8P1Yv/32yL4d4XfR5gBiy3xGO0onwll1QEbvtW96fIwhx2nejug0GTaEdNDoyr3fQ==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^27.2.4", + "@jest/test-result": "^27.2.4", + "@jest/transform": "^27.2.4", + "@jest/types": "^27.2.4", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.2", + "graceful-fs": "^4.2.4", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^4.0.3", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "jest-haste-map": "^27.2.4", + "jest-resolve": "^27.2.4", + "jest-util": "^27.2.4", + "jest-worker": "^27.2.4", + "slash": "^3.0.0", + "source-map": "^0.6.0", + "string-length": "^4.0.1", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^8.1.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/source-map": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.0.6.tgz", + "integrity": "sha512-Fek4mi5KQrqmlY07T23JRi0e7Z9bXTOOD86V/uS0EIW4PClvPDqZOyFlLpNJheS6QI0FNX1CgmPjtJ4EA/2M+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0", + "graceful-fs": "^4.2.4", + "source-map": "^0.6.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.2.4.tgz", + "integrity": "sha512-eU+PRo0+lIS01b0dTmMdVZ0TtcRSxEaYquZTRFMQz6CvsehGhx9bRzi9Zdw6VROviJyv7rstU+qAMX5pNBmnfQ==", + "dev": true, + "dependencies": { + "@jest/console": "^27.2.4", + "@jest/types": "^27.2.4", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.2.4.tgz", + "integrity": "sha512-fpk5eknU3/DXE2QCCG1wv/a468+cfPo3Asu6d6yUtM9LOPh709ubZqrhuUOYfM8hXMrIpIdrv1CdCrWWabX0rQ==", + "dev": true, + "dependencies": { + "@jest/test-result": "^27.2.4", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^27.2.4", + "jest-runtime": "^27.2.4" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.2.4.tgz", + "integrity": "sha512-n5FlX2TH0oQGwyVDKPxdJ5nI2sO7TJBFe3u3KaAtt7TOiV4yL+Y+rSFDl+Ic5MpbiA/eqXmLAQxjnBmWgS2rEA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.1.0", + "@jest/types": "^27.2.4", + "babel-plugin-istanbul": "^6.0.0", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^27.2.4", + "jest-regex-util": "^27.0.6", + "jest-util": "^27.2.4", + "micromatch": "^4.0.4", + "pirates": "^4.0.1", + "slash": "^3.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/types": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.2.4.tgz", + "integrity": "sha512-IDO2ezTxeMvQAHxzG/ZvEyA47q0aVfzT95rGFl7bZs/Go0aIucvfDbS2rmnoEdXxlLQhcolmoG/wvL/uKx4tKA==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@panva/asn1.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", + "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@sindresorhus/is": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.2.0.tgz", + "integrity": "sha512-VkE3KLBmJwcCaVARtQpfuKcKv8gcBmUubrfHGF84dXuuW6jgsRYxPtzcIhPyK9WAPpRt2/xY6zkD9MnRaJzSyw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@sinonjs/commons": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.0.1.tgz", + "integrity": "sha512-AU7kwFxreVd6OAXcAFlKSmZquiRUU0FvYm44k1Y1QbK7Co4m0aqfGMhjykIeQp/H6rcl+nFmj0zfdUcGVs9Dew==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true + }, + "node_modules/@types/babel__core": { + "version": "7.1.16", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.16.tgz", + "integrity": "sha512-EAEHtisTMM+KaKwfWdC3oyllIqswlznXCIVCt7/oRNrh+DhgT4UEBNC/jlADNjvw7UnfbcdkGQcPVZ1xYiLcrQ==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.3.tgz", + "integrity": "sha512-/GWCmzJWqV7diQW54smJZzWbSFf4QYtF71WCKhcx6Ru/tFyQIY2eiiITcCAeuPbNSvT9YCGkVMqqvSk2Z0mXiA==", + "dev": true, + "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.14.2", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.14.2.tgz", + "integrity": "sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.3.0" + } + }, + "node_modules/@types/bluebird": { + "version": "3.5.36", + "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.36.tgz", + "integrity": "sha512-HBNx4lhkxN7bx6P0++W8E289foSu8kO8GCk2unhuVggO+cE7rh9DhZUyPhUxNRG9m+5B5BTKxZQ5ZP92x/mx9Q==", + "dev": true + }, + "node_modules/@types/body-parser": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.1.tgz", + "integrity": "sha512-a6bTJ21vFOGIkwM0kzh9Yr89ziVxq4vYH2fQ6N8AeipEzai/cFK6aGMArIkUeIdRIgpwQa+2bXiLuUJCpSf2Cg==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", + "integrity": "sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "*", + "@types/node": "*", + "@types/responselike": "*" + } + }, + "node_modules/@types/compression": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.7.2.tgz", + "integrity": "sha512-lwEL4M/uAGWngWFLSG87ZDr2kLrbuR8p7X+QZB1OQlT+qkHsCPDVFnHPyXf4Vyl4yDDorNY+mAhosxkCvppatg==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "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/connect-pg-simple": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@types/connect-pg-simple/-/connect-pg-simple-4.2.4.tgz", + "integrity": "sha512-WzpKF3utq5iWK5k9ZSQ9edR8Y/HUhq4Nrz4cFSXdo6EJgv13RfLMbhDQCf8Xef4JbyhYmJSUovXmAp2oQzvj3w==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/express-session": "*", + "@types/pg": "*" + } + }, + "node_modules/@types/continuation-local-storage": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@types/continuation-local-storage/-/continuation-local-storage-3.2.3.tgz", + "integrity": "sha512-4LYeWblV+6puK9tFGM7Zr4OLZkVXmaL7hUK6/wHwbfwM+q7v+HZyBWTXkNOiC9GqOxv7ehhi5TMCbebZWeVYtw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cookie-parser": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.2.tgz", + "integrity": "sha512-uwcY8m6SDQqciHsqcKDGbo10GdasYsPCYkH3hVegj9qAah6pX5HivOnOuI3WYmyQMnOATV39zv/Ybs0bC/6iVg==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==", + "dev": true + }, + "node_modules/@types/cors": { + "version": "2.8.12", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", + "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==", + "dev": true + }, + "node_modules/@types/events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", + "dev": true + }, + "node_modules/@types/express": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", + "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.24", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.24.tgz", + "integrity": "sha512-3UJuW+Qxhzwjq3xhwXm2onQcFHn76frIYVbTu+kn24LFxI+dEhdfISDFovPB8VpEgW8oQCTpRuCe+0zJxB7NEA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "node_modules/@types/express-session": { + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.4.tgz", + "integrity": "sha512-7cNlSI8+oOBUHTfPXMwDxF/Lchx5aJ3ho7+p9jJZYVg9dVDJFh3qdMXmJtRsysnvS+C6x46k9DRYmrmCkE+MVg==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", + "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/hpp": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@types/hpp/-/hpp-0.2.2.tgz", + "integrity": "sha512-BLgsawqFFbS3tFUr+mcBRfst+DumnSfi4PgyNeJAGk0eIxm7lKX1axmHVlbgKNAZS0caZA5/LSopuj0T2LKRPw==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", + "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", + "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", + "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": "27.0.2", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.0.2.tgz", + "integrity": "sha512-4dRxkS/AFX0c5XW6IPMNOydLn2tEhNhJV7DnYK+0bjoJZ+QTmfucBlihX7aoEsh/ocYtkLC73UbnBXBXIxsULA==", + "dev": true, + "dependencies": { + "jest-diff": "^27.0.0", + "pretty-format": "^27.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", + "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", + "dev": true + }, + "node_modules/@types/keyv": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.3.tgz", + "integrity": "sha512-FXCJgyyN3ivVgRoml4h94G/p3kY+u/B86La+QptcqJaWtBWtmc6TtkNfS40n9bIvyLteHh7zXOtgbobORKPbDg==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/lodash": { + "version": "4.14.175", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.175.tgz", + "integrity": "sha512-XmdEOrKQ8a1Y/yxQFOMbC47G/V2VDO1GvMRnl4O75M4GW/abC5tnfzadQYkqEveqRM1dEJGFFegfPNA2vvx2iw==", + "dev": true + }, + "node_modules/@types/marked": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/marked/-/marked-3.0.1.tgz", + "integrity": "sha512-jry/WUAC511P2NBCeiCkfTRCN2VXobeeQa8p8gImOYsRfnuIVfeEsqOJ1pk+CzCwfMCdv3dkTQRCYaNkkFGtxw==", + "dev": true + }, + "node_modules/@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", + "dev": true + }, + "node_modules/@types/morgan": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.3.tgz", + "integrity": "sha512-BiLcfVqGBZCyNCnCH3F4o2GmDLrpy0HeBVnNlyZG4fo88ZiE9SoiBe3C+2ezuwbjlEyT+PDZ17//TAlRxAn75Q==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/multer": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.7.tgz", + "integrity": "sha512-/SNsDidUFCvqqcWDwxv2feww/yqhNeTRL5CVoL3jU4Goc4kKEL10T7Eye65ZqPNi4HRx8sAEX59pV1aEH7drNA==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/node": { + "version": "14.17.20", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.20.tgz", + "integrity": "sha512-gI5Sl30tmhXsqkNvopFydP7ASc4c2cLfGNQrVKN3X90ADFWFsPEsotm/8JHSUJQKTHbwowAHtcJPeyVhtKv0TQ==" + }, + "node_modules/@types/node-jose": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@types/node-jose/-/node-jose-1.1.8.tgz", + "integrity": "sha512-AFcArbplUaO+DqGVEPaiz/guw3uUA+dRHjaj26EEDF0DmTEPUd3dEdfdJMUx4kD65EAR3TnI1iHIcb31+Ko87Q==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/passport": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.7.tgz", + "integrity": "sha512-JtswU8N3kxBYgo+n9of7C97YQBT+AYPP2aBfNGTzABqPAZnK/WOAaKfh3XesUYMZRrXFuoPc2Hv0/G/nQFveHw==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/pg": { + "version": "7.4.8", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-7.4.8.tgz", + "integrity": "sha512-naSYLMOIBU+/BddUlQUyQuba9reG5yN0MR7g4EcN/AEt3zll48fGozX8s7xjaXuKmFOS1qqOrr/ZeF6SSOjKCw==", + "dev": true, + "dependencies": { + "@types/events": "*", + "@types/node": "*", + "@types/pg-types": "*" + } + }, + "node_modules/@types/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-a+fLdul8OczRvPWPf8eTb6wPhxzyWQwRGhNN0ugtOtk6yFOG53i2LwXaA0d2D6bsJlWxi6eCuGZLGoCcdOlWZA==", + "deprecated": "This is a stub types definition. pg-types provides its own type definitions, so you do not need this installed.", + "dev": true, + "dependencies": { + "pg-types": "*" + } + }, + "node_modules/@types/prettier": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.1.tgz", + "integrity": "sha512-Fo79ojj3vdEZOHg3wR9ksAMRz4P3S5fDB5e/YWZiFnyFQI1WY2Vftu9XoXVVtJfxB7Bpce/QTqWSSntkz2Znrw==", + "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/responselike": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", + "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/sequelize": { + "version": "4.28.10", + "resolved": "https://registry.npmjs.org/@types/sequelize/-/sequelize-4.28.10.tgz", + "integrity": "sha512-GKbEbl6uyEYTPvU2JZvmqZHfpwTTjaZvNSd2gFJrhcxUL1bcyG7i+S8Od2L0/+skrk2bBINl7J1Sugo0mgIY3g==", + "dev": true, + "dependencies": { + "@types/bluebird": "*", + "@types/continuation-local-storage": "*", + "@types/lodash": "*", + "@types/validator": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.13.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", + "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/showdown": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/showdown/-/showdown-1.9.4.tgz", + "integrity": "sha512-50ehC3IAijfkvoNqmQ+VL73S7orOxmAK8ljQAFBv8o7G66lAZyxQj1L3BAv2dD86myLXI+sgKP1kcxAaxW356w==", + "dev": true + }, + "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/superagent": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-4.1.13.tgz", + "integrity": "sha512-YIGelp3ZyMiH0/A09PMAORO0EBGlF5xIKfDpK74wdYvWUs2o96b5CItJcWPdH409b7SAXIIG6p8NdU/4U2Maww==", + "dev": true, + "dependencies": { + "@types/cookiejar": "*", + "@types/node": "*" + } + }, + "node_modules/@types/supertest": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.11.tgz", + "integrity": "sha512-uci4Esokrw9qGb9bvhhSVEjd6rkny/dk5PK/Qz4yxKiyppEI+dOPlNrZBahE3i+PoKFYyDxChVXZ/ysS/nrm1Q==", + "dev": true, + "dependencies": { + "@types/superagent": "*" + } + }, + "node_modules/@types/uuid": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.1.tgz", + "integrity": "sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg==", + "dev": true + }, + "node_modules/@types/validator": { + "version": "13.6.3", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.6.3.tgz", + "integrity": "sha512-fWG42pMJOL4jKsDDZZREnXLjc3UE0R8LOJfARWYg6U966rxDT7TYejYzLnUF5cvSObGg34nd0+H2wHHU5Omdfw==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", + "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "4.32.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.32.0.tgz", + "integrity": "sha512-+OWTuWRSbWI1KDK8iEyG/6uK2rTm3kpS38wuVifGUTDB6kjEuNrzBI1MUtxnkneuWG/23QehABe2zHHrj+4yuA==", + "dev": true, + "dependencies": { + "@typescript-eslint/experimental-utils": "4.32.0", + "@typescript-eslint/scope-manager": "4.32.0", + "debug": "^4.3.1", + "functional-red-black-tree": "^1.0.1", + "ignore": "^5.1.8", + "regexpp": "^3.1.0", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^4.0.0", + "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/experimental-utils": { + "version": "4.32.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.32.0.tgz", + "integrity": "sha512-WLoXcc+cQufxRYjTWr4kFt0DyEv6hDgSaFqYhIzQZ05cF+kXfqXdUh+//kgquPJVUBbL3oQGKQxwPbLxHRqm6A==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.7", + "@typescript-eslint/scope-manager": "4.32.0", + "@typescript-eslint/types": "4.32.0", + "@typescript-eslint/typescript-estree": "4.32.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "4.32.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.32.0.tgz", + "integrity": "sha512-lhtYqQ2iEPV5JqV7K+uOVlPePjClj4dOw7K4/Z1F2yvjIUvyr13yJnDzkK6uon4BjHYuHy3EG0c2Z9jEhFk56w==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "4.32.0", + "@typescript-eslint/types": "4.32.0", + "@typescript-eslint/typescript-estree": "4.32.0", + "debug": "^4.3.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "4.32.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.32.0.tgz", + "integrity": "sha512-DK+fMSHdM216C0OM/KR1lHXjP1CNtVIhJ54kQxfOE6x8UGFAjha8cXgDMBEIYS2XCYjjCtvTkjQYwL3uvGOo0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "4.32.0", + "@typescript-eslint/visitor-keys": "4.32.0" + }, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "4.32.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.32.0.tgz", + "integrity": "sha512-LE7Z7BAv0E2UvqzogssGf1x7GPpUalgG07nGCBYb1oK4mFsOiFC/VrSMKbZQzFJdN2JL5XYmsx7C7FX9p9ns0w==", + "dev": true, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "4.32.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.32.0.tgz", + "integrity": "sha512-tRYCgJ3g1UjMw1cGG8Yn1KzOzNlQ6u1h9AmEtPhb5V5a1TmiHWcRyF/Ic+91M4f43QeChyYlVTcf3DvDTZR9vw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "4.32.0", + "@typescript-eslint/visitor-keys": "4.32.0", + "debug": "^4.3.1", + "globby": "^11.0.3", + "is-glob": "^4.0.1", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "4.32.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.32.0.tgz", + "integrity": "sha512-e7NE0qz8W+atzv3Cy9qaQ7BTLwWsm084Z0c4nIO2l3Bp6u9WIgdqCgyPyV5oSPDMIW3b20H59OOCmVk3jw3Ptw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "4.32.0", + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/abab": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", + "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", + "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.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dependencies": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "dev": true, + "dependencies": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dev": true, + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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-escapes/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/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/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY=" + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "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": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.1.tgz", + "integrity": "sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "node_modules/babel-jest": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.2.4.tgz", + "integrity": "sha512-f24OmxyWymk5jfgLdlCMu4fTs4ldxFBIdn5sJdhvGC1m08rSkJ5hYbWkNmfBSvE/DjhCVNSHXepxsI6THGfGsg==", + "dev": true, + "dependencies": { + "@jest/transform": "^27.2.4", + "@jest/types": "^27.2.4", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.0.0", + "babel-preset-jest": "^27.2.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "slash": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", + "integrity": "sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^4.0.0", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "27.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.2.0.tgz", + "integrity": "sha512-TOux9khNKdi64mW+0OIhcmbAn75tTlzKhxmiNXevQaPbrBYK7YKjP1jl6NHTJ6XR5UgUrJbCnWlKVnJn29dfjw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.0.0", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.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": "27.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.2.0.tgz", + "integrity": "sha512-z7MgQ3peBwN5L5aCqBKnF6iqdlvZvFUQynEhu0J+X9nHLU72jO3iY331lcYrg+AssJ8q7xsv5/3AICzVmJ/wvg==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^27.2.0", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.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/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "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/base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/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/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "node_modules/body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "dependencies": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/boxen": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", + "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", + "dev": true, + "dependencies": { + "ansi-align": "^3.0.0", + "camelcase": "^6.2.0", + "chalk": "^4.1.0", + "cli-boxes": "^2.2.1", + "string-width": "^4.2.2", + "type-fest": "^0.20.2", + "widest-line": "^3.1.0", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true + }, + "node_modules/browserslist": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.17.2.tgz", + "integrity": "sha512-jSDZyqJmkKMEMi7SZAgX5UltFdR5NAO43vY0AwTpu4X3sGH7GLLQ83KiUomgrnvZRCeW0yPPnKqnxPqQOER9zQ==", + "dev": true, + "dependencies": { + "caniuse-lite": "^1.0.30001261", + "electron-to-chromium": "^1.3.854", + "escalade": "^3.1.1", + "nanocolors": "^0.2.12", + "node-releases": "^1.1.76" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + } + }, + "node_modules/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": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "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==" + }, + "node_modules/buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/busboy": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", + "integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=", + "dependencies": { + "dicer": "0.2.5", + "readable-stream": "1.1.x" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", + "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "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.30001261", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001261.tgz", + "integrity": "sha512-vM8D9Uvp7bHIN0fZ2KQ4wnmYFpJo/Etb4Vwsuc+ka0tfGDHvOPrFm6S/7CCNLSOkAUjenT2HnUPESdOIL91FaA==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + } + }, + "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.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/ci-info": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.2.0.tgz", + "integrity": "sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A==", + "dev": true + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", + "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "dev": true + }, + "node_modules/class-transformer": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.3.1.tgz", + "integrity": "sha512-cKFwohpJbuMovS8xVLmn8N2AUbAuc8pVo4zEfsUVo8qgECOogns1WVk/FkOZoxhOPTyTYFckuoH+13FO+MQ8GA==" + }, + "node_modules/class-validator": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.12.2.tgz", + "integrity": "sha512-TDzPzp8BmpsbPhQpccB3jMUE/3pK0TyqamrK0kcx+ZeFytMA+O6q87JZZGObHHnoo9GM8vl/JppIyKWeEA/EVw==", + "dependencies": { + "@types/validator": "13.0.0", + "google-libphonenumber": "^3.2.8", + "tslib": ">=1.9.0", + "validator": "13.0.0" + } + }, + "node_modules/class-validator/node_modules/@types/validator": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.0.0.tgz", + "integrity": "sha512-WAy5txG7aFX8Vw3sloEKp5p/t/Xt8jD3GRD9DacnFv6Vo8ubudAsRTXgxpQwU0mpzY/H8U4db3roDuCMjShBmw==" + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-boxes": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "dependencies": { + "mimic-response": "^1.0.0" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "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": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", + "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", + "dependencies": { + "color-convert": "^1.9.1", + "color-string": "^1.5.2" + } + }, + "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==" + }, + "node_modules/color-string": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.6.0.tgz", + "integrity": "sha512-c/hGS+kRWJutUBEngKKmk4iH3sD59MBkoxVapS/0wgpCz2u7XsNloxknyvBhzwEs1IbV36D9PwqLPJ2DTu3vMA==", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/color/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/colorspace": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz", + "integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==", + "dependencies": { + "color": "3.0.x", + "text-hex": "1.0.x" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/concat-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "node_modules/concat-stream/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/concat-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "dev": true, + "dependencies": { + "dot-prop": "^5.2.0", + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/connect-pg-simple": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/connect-pg-simple/-/connect-pg-simple-6.2.1.tgz", + "integrity": "sha512-bwDp/gKyRtyz0V5Vxy3SATSxItWBK/wDhaacncC79+q1B1VB8SQ49AlVaQCM+XxmIO29cWX4cvsFj65mD2qrzA==", + "dependencies": { + "@types/pg": "^7.14.4", + "pg": "^8.2.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/connect-pg-simple/node_modules/@types/pg": { + "version": "7.14.11", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-7.14.11.tgz", + "integrity": "sha512-EnZkZ1OMw9DvNfQkn2MTJrwKmhJYDEs5ujWrPfvseWNoI95N8B4HzU/Ltrq5ZfYxDX/Zg8mTzwr6UAyTjjFvXA==", + "dependencies": { + "@types/node": "*", + "pg-protocol": "^1.2.0", + "pg-types": "^2.2.0" + } + }, + "node_modules/connect-pg-simple/node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/connect-pg-simple/node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-parser": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.5.tgz", + "integrity": "sha512-f13bPUj/gG/5mDr+xLmSxxDsB9DQiTIfhJS/sqjrmfAWiAN+x2O4i/XguTL9yDZ+/IFDanJ+5x7hC4CXT9Tdzw==", + "dependencies": { + "cookie": "0.4.0", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "node_modules/cookiejar": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", + "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==", + "dev": true + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "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==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssom": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", + "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", + "dev": true + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + }, + "node_modules/data-urls": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", + "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "dev": true, + "dependencies": { + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", + "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==", + "dev": true + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", + "dev": true + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "engines": { + "node": ">=10" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dicer": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", + "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=", + "dependencies": { + "readable-stream": "1.1.x", + "streamsearch": "0.1.2" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.0.6.tgz", + "integrity": "sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ==", + "dev": true, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/domexception": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", + "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", + "dev": true, + "dependencies": { + "webidl-conversions": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/domexception/node_modules/webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dotenv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", + "engines": { + "node": ">=10" + } + }, + "node_modules/dottie": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.2.tgz", + "integrity": "sha512-fmrwR04lsniq/uSr8yikThDTrM7epXHBAAjH9TbeH3rEA8tdCO7mRzB9hdmdGyJCxF8KERo9CITcm3kGuoyMhg==" + }, + "node_modules/duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "dev": true + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "node_modules/electron-to-chromium": { + "version": "1.3.855", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.855.tgz", + "integrity": "sha512-4g104x65rHM+Wv/0VPh36s6YRBXP4mebpLSNtwTOpUvxyzAmdQGPoxYdg4MiaxvQnJh2xxcAwDOXYa1SMRr2WQ==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", + "integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "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/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, + "node_modules/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-goat": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", + "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escodegen": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", + "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "dev": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/escodegen/node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint": { + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", + "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.3", + "@humanwhocodes/config-array": "^0.5.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-7.2.0.tgz", + "integrity": "sha512-rV4Qu0C3nfJKPOAhFujFxB7RMP+URFyQqqOZW9DMRD7ZDTFyjaIlETU3xzHELt++4ugC0+Jm084HQYkkJe+Ivg==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz", + "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0" + }, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "eslint": ">=5.0.0", + "prettier": ">=1.13.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint/node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "dependencies": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "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": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/expect/-/expect-27.2.4.tgz", + "integrity": "sha512-gOtuonQ8TCnbNNCSw2fhVzRf8EFYDII4nB5NmG4IEV0rbUnW1I5zXvoTntU4iicB/Uh0oZr20NGlOLdJiwsOZA==", + "dev": true, + "dependencies": { + "@jest/types": "^27.2.4", + "ansi-styles": "^5.0.0", + "jest-get-type": "^27.0.6", + "jest-matcher-utils": "^27.2.4", + "jest-message-util": "^27.2.4", + "jest-regex-util": "^27.0.6" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/expect/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/express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "dependencies": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express-session": { + "version": "1.17.2", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.2.tgz", + "integrity": "sha512-mPcYcLA0lvh7D4Oqr5aNJFMtBMKPLl++OKKxkHzZ0U0oDq1rpKBnkR5f5vCHR26VeArlTOEF9td4x5IjICksRQ==", + "dependencies": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.0.2", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.1", + "uid-safe": "~2.1.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/express-session/node_modules/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express-session/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/express-session/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express-session/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/express-session/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/express/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/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/fast-csv": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/fast-csv/-/fast-csv-4.3.6.tgz", + "integrity": "sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==", + "dependencies": { + "@fast-csv/format": "4.3.5", + "@fast-csv/parse": "4.3.6" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", + "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", + "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fecha": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.1.tgz", + "integrity": "sha512-MMMQ0ludy/nBs1/o0zVOiKTpG7qMbonKUzjJgQFEuvq6INZ1OraKPRAWkBq5vlKLOUMpmNYG1JoN3oDPUQ9m3Q==" + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/file-stream-rotator": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.5.7.tgz", + "integrity": "sha512-VYb3HZ/GiAGUCrfeakO8Mp54YGswNUHvL7P09WQcXAJNSj3iQ5QraYSp3cIn1MUyw6uzfgN/EFOarCNa4JvUHQ==", + "dependencies": { + "moment": "^2.11.2" + } + }, + "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.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", + "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", + "dev": true + }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + }, + "node_modules/form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formidable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", + "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==", + "dev": true, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/global-dirs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", + "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", + "dev": true, + "dependencies": { + "ini": "2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globals": { + "version": "13.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.11.0.tgz", + "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", + "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/google-libphonenumber": { + "version": "3.2.24", + "resolved": "https://registry.npmjs.org/google-libphonenumber/-/google-libphonenumber-3.2.24.tgz", + "integrity": "sha512-5Z3dZTgwacTjALlkRK5hTIjGLwTCJGNZtmWgnhpb1Z6XEBYGsXbJGsr9+MheP/2mAk/ssOvpvMXKEZISWeytcA==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/got": { + "version": "11.8.2", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.2.tgz", + "integrity": "sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.1", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-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.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-yarn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", + "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/helmet": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-4.6.0.tgz", + "integrity": "sha512-HVqALKZlR95ROkrnesdhbbZJFi/rIVSoNq6f3jA/9u6MIbTsPh3xZwihjeI5+DO/2sOV6HMHooXcEOuwskHpTg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/hpp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/hpp/-/hpp-0.2.3.tgz", + "integrity": "sha512-4zDZypjQcxK/8pfFNR7jaON7zEUpXZxz4viyFmqjb3kWNWAHsLEUmWXcdn25c5l76ISvnD6hbOGO97cXUI3Ryw==", + "dependencies": { + "lodash": "^4.17.12", + "type-is": "^1.6.12" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", + "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "dev": true, + "dependencies": { + "whatwg-encoding": "^1.0.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" + }, + "node_modules/http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "dev": true, + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", + "dev": true + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/import-local": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", + "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/inflection": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.13.1.tgz", + "integrity": "sha512-dldYtl2WlN0QDkIDtg8+xFwOS2Tbmp12t1cHa5/YClU6ZQjTFm7B66UcVbh9NQB+HvT5BAd2t5+yKsBkw5pcqA==", + "engines": [ + "node >= 0.4.0" + ] + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "node_modules/ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "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.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, + "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-ci": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.0.tgz", + "integrity": "sha512-kDXyttuLeslKAHYL/K28F2YkM3x5jvFPEw3yXbRptXydjD9rpLEz+C5K5iutY9ZiUu6AP41JdvRQwF4Iqs4ZCQ==", + "dev": true, + "dependencies": { + "ci-info": "^3.1.1" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-core-module": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.7.0.tgz", + "integrity": "sha512-ByY+tjCciCr+9nLryBYcSD50EOGWt95c7tIsKTG1J2ixKKXPvF7Ej3AVd+UfDydAJom3biBGDBALaO79ktwgEQ==", + "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": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-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-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "dev": true, + "dependencies": { + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-npm": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz", + "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "node_modules/is-yarn-global": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", + "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", + "dev": true + }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.1.tgz", + "integrity": "sha512-GvCYYTxaCPqwMjobtVcVKvSHtAGe48MNhGjpK8LtVF8K0ISX7hCKl85LgtuaSneWVyQmaGcW3iXVV3GaZSLpmQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_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.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", + "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-reports": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", + "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest/-/jest-27.2.4.tgz", + "integrity": "sha512-h4uqb1EQLfPulWyUFFWv9e9Nn8sCqsJ/j3wk/KCY0p4s4s0ICCfP3iMf6hRf5hEhsDyvyrCgKiZXma63gMz16A==", + "dev": true, + "dependencies": { + "@jest/core": "^27.2.4", + "import-local": "^3.0.2", + "jest-cli": "^27.2.4" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.2.4.tgz", + "integrity": "sha512-eeO1C1u4ex7pdTroYXezr+rbr957myyVoKGjcY4R1TJi3A+9v+4fu1Iv9J4eLq1bgFyT3O3iRWU9lZsEE7J72Q==", + "dev": true, + "dependencies": { + "@jest/types": "^27.2.4", + "execa": "^5.0.0", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-circus": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.2.4.tgz", + "integrity": "sha512-TtheheTElrGjlsY9VxkzUU1qwIx05ItIusMVKnvNkMt4o/PeegLRcjq3Db2Jz0GGdBalJdbzLZBgeulZAJxJWA==", + "dev": true, + "dependencies": { + "@jest/environment": "^27.2.4", + "@jest/test-result": "^27.2.4", + "@jest/types": "^27.2.4", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "expect": "^27.2.4", + "is-generator-fn": "^2.0.0", + "jest-each": "^27.2.4", + "jest-matcher-utils": "^27.2.4", + "jest-message-util": "^27.2.4", + "jest-runtime": "^27.2.4", + "jest-snapshot": "^27.2.4", + "jest-util": "^27.2.4", + "pretty-format": "^27.2.4", + "slash": "^3.0.0", + "stack-utils": "^2.0.3", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-cli": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.2.4.tgz", + "integrity": "sha512-4kpQQkg74HYLaXo3nzwtg4PYxSLgL7puz1LXHj5Tu85KmlIpxQFjRkXlx4V47CYFFIDoyl3rHA/cXOxUWyMpNg==", + "dev": true, + "dependencies": { + "@jest/core": "^27.2.4", + "@jest/test-result": "^27.2.4", + "@jest/types": "^27.2.4", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "import-local": "^3.0.2", + "jest-config": "^27.2.4", + "jest-util": "^27.2.4", + "jest-validate": "^27.2.4", + "prompts": "^2.0.1", + "yargs": "^16.2.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.2.4.tgz", + "integrity": "sha512-tWy0UxhdzqiKyp4l5Vq4HxLyD+gH5td+GCF3c22/DJ0bYAOsMo+qi2XtbJI6oYMH5JOJQs9nLW/r34nvFCehjA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.1.0", + "@jest/test-sequencer": "^27.2.4", + "@jest/types": "^27.2.4", + "babel-jest": "^27.2.4", + "chalk": "^4.0.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.1", + "graceful-fs": "^4.2.4", + "is-ci": "^3.0.0", + "jest-circus": "^27.2.4", + "jest-environment-jsdom": "^27.2.4", + "jest-environment-node": "^27.2.4", + "jest-get-type": "^27.0.6", + "jest-jasmine2": "^27.2.4", + "jest-regex-util": "^27.0.6", + "jest-resolve": "^27.2.4", + "jest-runner": "^27.2.4", + "jest-util": "^27.2.4", + "jest-validate": "^27.2.4", + "micromatch": "^4.0.4", + "pretty-format": "^27.2.4" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.2.4.tgz", + "integrity": "sha512-bLAVlDSCR3gqUPGv+4nzVpEXGsHh98HjUL7Vb2hVyyuBDoQmja8eJb0imUABsuxBeUVmf47taJSAd9nDrwWKEg==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^27.0.6", + "jest-get-type": "^27.0.6", + "pretty-format": "^27.2.4" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.0.6.tgz", + "integrity": "sha512-Fid6dPcjwepTFraz0YxIMCi7dejjJ/KL9FBjPYhBp4Sv1Y9PdhImlKZqYU555BlN4TQKaTc+F2Av1z+anVyGkA==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-each": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.2.4.tgz", + "integrity": "sha512-w9XVc+0EDBUTJS4xBNJ7N2JCcWItFd006lFjz77OarAQcQ10eFDBMrfDv2GBJMKlXe9aq0HrIIF51AXcZrRJyg==", + "dev": true, + "dependencies": { + "@jest/types": "^27.2.4", + "chalk": "^4.0.0", + "jest-get-type": "^27.0.6", + "jest-util": "^27.2.4", + "pretty-format": "^27.2.4" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-environment-jsdom": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.2.4.tgz", + "integrity": "sha512-X70pTXFSypD7AIzKT1mLnDi5hP9w9mdTRcOGOmoDoBrNyNEg4rYm6d4LQWFLc9ps1VnMuDOkFSG0wjSNYGjkng==", + "dev": true, + "dependencies": { + "@jest/environment": "^27.2.4", + "@jest/fake-timers": "^27.2.4", + "@jest/types": "^27.2.4", + "@types/node": "*", + "jest-mock": "^27.2.4", + "jest-util": "^27.2.4", + "jsdom": "^16.6.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.2.4.tgz", + "integrity": "sha512-ZbVbFSnbzTvhLOIkqh5lcLuGCCFvtG4xTXIRPK99rV2KzQT3kNg16KZwfTnLNlIiWCE8do960eToeDfcqmpSAw==", + "dev": true, + "dependencies": { + "@jest/environment": "^27.2.4", + "@jest/fake-timers": "^27.2.4", + "@jest/types": "^27.2.4", + "@types/node": "*", + "jest-mock": "^27.2.4", + "jest-util": "^27.2.4" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.6.tgz", + "integrity": "sha512-XTkK5exIeUbbveehcSR8w0bhH+c0yloW/Wpl+9vZrjzztCPWrxhHwkIFpZzCt71oRBsgxmuUfxEqOYoZI2macg==", + "dev": true, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.2.4.tgz", + "integrity": "sha512-bkJ4bT00T2K+1NZXbRcyKnbJ42I6QBvoDNMTAQQDBhaGNnZreiQKUNqax0e6hLTx7E75pKDeltVu3V1HAdu+YA==", + "dev": true, + "dependencies": { + "@jest/types": "^27.2.4", + "@types/graceful-fs": "^4.1.2", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.4", + "jest-regex-util": "^27.0.6", + "jest-serializer": "^27.0.6", + "jest-util": "^27.2.4", + "jest-worker": "^27.2.4", + "micromatch": "^4.0.4", + "walker": "^1.0.7" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-jasmine2": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.2.4.tgz", + "integrity": "sha512-fcffjO/xLWLVnW2ct3No4EksxM5RyPwHDYu9QU+90cC+/eSMLkFAxS55vkqsxexOO5zSsZ3foVpMQcg/amSeIQ==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.1.0", + "@jest/environment": "^27.2.4", + "@jest/source-map": "^27.0.6", + "@jest/test-result": "^27.2.4", + "@jest/types": "^27.2.4", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "expect": "^27.2.4", + "is-generator-fn": "^2.0.0", + "jest-each": "^27.2.4", + "jest-matcher-utils": "^27.2.4", + "jest-message-util": "^27.2.4", + "jest-runtime": "^27.2.4", + "jest-snapshot": "^27.2.4", + "jest-util": "^27.2.4", + "pretty-format": "^27.2.4", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-leak-detector": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.2.4.tgz", + "integrity": "sha512-SrcHWbe0EHg/bw2uBjVoHacTo5xosl068x2Q0aWsjr2yYuW2XwqrSkZV4lurUop0jhv1709ymG4or+8E4sH27Q==", + "dev": true, + "dependencies": { + "jest-get-type": "^27.0.6", + "pretty-format": "^27.2.4" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.2.4.tgz", + "integrity": "sha512-nQeLfFAIPPkyhkDfifAPfP/U5wm1x0fLtAzqXZSSKckXDNuk2aaOfQiDYv1Mgf5GY6yOsxfUnvNm3dDjXM+BXw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^27.2.4", + "jest-get-type": "^27.0.6", + "pretty-format": "^27.2.4" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.2.4.tgz", + "integrity": "sha512-wbKT/BNGnBVB9nzi+IoaLkXt6fbSvqUxx+IYY66YFh96J3goY33BAaNG3uPqaw/Sh/FR9YpXGVDfd5DJdbh4nA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^27.2.4", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.4", + "pretty-format": "^27.2.4", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-message-util/node_modules/@babel/code-frame": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/jest-mock": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.2.4.tgz", + "integrity": "sha512-iVRU905rutaAoUcrt5Tm1JoHHWi24YabqEGXjPJI4tAyA6wZ7mzDi3GrZ+M7ebgWBqUkZE93GAx1STk7yCMIQA==", + "dev": true, + "dependencies": { + "@jest/types": "^27.2.4", + "@types/node": "*" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", + "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.0.6.tgz", + "integrity": "sha512-SUhPzBsGa1IKm8hx2F4NfTGGp+r7BXJ4CulsZ1k2kI+mGLG+lxGrs76veN2LF/aUdGosJBzKgXmNCw+BzFqBDQ==", + "dev": true, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.2.4.tgz", + "integrity": "sha512-IsAO/3+3BZnKjI2I4f3835TBK/90dxR7Otgufn3mnrDFTByOSXclDi3G2XJsawGV4/18IMLARJ+V7Wm7t+J89Q==", + "dev": true, + "dependencies": { + "@jest/types": "^27.2.4", + "chalk": "^4.0.0", + "escalade": "^3.1.1", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^27.2.4", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^27.2.4", + "jest-validate": "^27.2.4", + "resolve": "^1.20.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.2.4.tgz", + "integrity": "sha512-i5s7Uh9B3Q6uwxLpMhNKlgBf6pcemvWaORxsW1zNF/YCY3jd5EftvnGBI+fxVwJ1CBxkVfxqCvm1lpZkbaoGmg==", + "dev": true, + "dependencies": { + "@jest/types": "^27.2.4", + "jest-regex-util": "^27.0.6", + "jest-snapshot": "^27.2.4" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-runner": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.2.4.tgz", + "integrity": "sha512-hIo5PPuNUyVDidZS8EetntuuJbQ+4IHWxmHgYZz9FIDbG2wcZjrP6b52uMDjAEQiHAn8yn8ynNe+TL8UuGFYKg==", + "dev": true, + "dependencies": { + "@jest/console": "^27.2.4", + "@jest/environment": "^27.2.4", + "@jest/test-result": "^27.2.4", + "@jest/transform": "^27.2.4", + "@jest/types": "^27.2.4", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.8.1", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "jest-docblock": "^27.0.6", + "jest-environment-jsdom": "^27.2.4", + "jest-environment-node": "^27.2.4", + "jest-haste-map": "^27.2.4", + "jest-leak-detector": "^27.2.4", + "jest-message-util": "^27.2.4", + "jest-resolve": "^27.2.4", + "jest-runtime": "^27.2.4", + "jest-util": "^27.2.4", + "jest-worker": "^27.2.4", + "source-map-support": "^0.5.6", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.2.4.tgz", + "integrity": "sha512-ICKzzYdjIi70P17MZsLLIgIQFCQmIjMFf+xYww3aUySiUA/QBPUTdUqo5B2eg4HOn9/KkUsV0z6GVgaqAPBJvg==", + "dev": true, + "dependencies": { + "@jest/console": "^27.2.4", + "@jest/environment": "^27.2.4", + "@jest/fake-timers": "^27.2.4", + "@jest/globals": "^27.2.4", + "@jest/source-map": "^27.0.6", + "@jest/test-result": "^27.2.4", + "@jest/transform": "^27.2.4", + "@jest/types": "^27.2.4", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "execa": "^5.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^27.2.4", + "jest-message-util": "^27.2.4", + "jest-mock": "^27.2.4", + "jest-regex-util": "^27.0.6", + "jest-resolve": "^27.2.4", + "jest-snapshot": "^27.2.4", + "jest-util": "^27.2.4", + "jest-validate": "^27.2.4", + "slash": "^3.0.0", + "strip-bom": "^4.0.0", + "yargs": "^16.2.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-serializer": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.0.6.tgz", + "integrity": "sha512-PtGdVK9EGC7dsaziskfqaAPib6wTViY3G8E5wz9tLVPhHyiDNTZn/xjZ4khAw+09QkoOVpn7vF5nPSN6dtBexA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "graceful-fs": "^4.2.4" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.2.4.tgz", + "integrity": "sha512-5DFxK31rYS8X8C6WXsFx8XxrxW3PGa6+9IrUcZdTLg1aEyXDGIeiBh4jbwvh655bg/9vTETbEj/njfZicHTZZw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.7.2", + "@babel/generator": "^7.7.2", + "@babel/parser": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.0.0", + "@jest/transform": "^27.2.4", + "@jest/types": "^27.2.4", + "@types/babel__traverse": "^7.0.4", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^27.2.4", + "graceful-fs": "^4.2.4", + "jest-diff": "^27.2.4", + "jest-get-type": "^27.0.6", + "jest-haste-map": "^27.2.4", + "jest-matcher-utils": "^27.2.4", + "jest-message-util": "^27.2.4", + "jest-resolve": "^27.2.4", + "jest-util": "^27.2.4", + "natural-compare": "^1.4.0", + "pretty-format": "^27.2.4", + "semver": "^7.3.2" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-util": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.2.4.tgz", + "integrity": "sha512-mW++4u+fSvAt3YBWm5IpbmRAceUqa2B++JlUZTiuEt2AmNYn0Yw5oay4cP17TGsMINRNPSGiJ2zNnX60g+VbFg==", + "dev": true, + "dependencies": { + "@jest/types": "^27.2.4", + "@types/node": "*", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^3.0.0", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-validate": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.2.4.tgz", + "integrity": "sha512-VMtbxbkd7LHnIH7PChdDtrluCFRJ4b1YV2YJzNwwsASMWftq/HgqiqjvptBOWyWOtevgO3f14wPxkPcLlVBRog==", + "dev": true, + "dependencies": { + "@jest/types": "^27.2.4", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^27.0.6", + "leven": "^3.1.0", + "pretty-format": "^27.2.4" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.2.4.tgz", + "integrity": "sha512-LXC/0+dKxhK7cfF7reflRYlzDIaQE+fL4ynhKhzg8IMILNMuI4xcjXXfUJady7OR4/TZeMg7X8eHx8uan9vqaQ==", + "dev": true, + "dependencies": { + "@jest/test-result": "^27.2.4", + "@jest/types": "^27.2.4", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "jest-util": "^27.2.4", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-worker": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.2.4.tgz", + "integrity": "sha512-Zq9A2Pw59KkVjBBKD1i3iE2e22oSjXhUKKuAK1HGX8flGwkm6NMozyEYzKd41hXc64dbd/0eWFeEEuxqXyhM+g==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.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/jose": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.5.tgz", + "integrity": "sha512-BAiDNeDKTMgk4tvD0BbxJ8xHEHBZgpeRZ1zGPPsitSyMgjoMWiLGYAE7H7NpP5h0lPppQajQs871E8NHUrzVPA==", + "dependencies": { + "@panva/asn1.js": "^1.0.0" + }, + "engines": { + "node": ">=10.13.0 < 13 || >=13.7.0" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-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/jsdom": { + "version": "16.7.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", + "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", + "dev": true, + "dependencies": { + "abab": "^2.0.5", + "acorn": "^8.2.4", + "acorn-globals": "^6.0.0", + "cssom": "^0.4.4", + "cssstyle": "^2.3.0", + "data-urls": "^2.0.0", + "decimal.js": "^10.2.1", + "domexception": "^2.0.1", + "escodegen": "^2.0.0", + "form-data": "^3.0.0", + "html-encoding-sniffer": "^2.0.1", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.0", + "parse5": "6.0.1", + "saxes": "^5.0.1", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.0.0", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^2.0.0", + "webidl-conversions": "^6.1.0", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.5.0", + "ws": "^7.4.6", + "xml-name-validator": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/acorn": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", + "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", + "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz", + "integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==", + "dependencies": { + "json-buffer": "3.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/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + }, + "node_modules/latest-version": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", + "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", + "dev": true, + "dependencies": { + "package-json": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/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.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=" + }, + "node_modules/lodash.groupby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", + "integrity": "sha1-Cwih3PaDl8OXhVwyOXg4Mt90A9E=" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" + }, + "node_modules/lodash.isfunction": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", + "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==" + }, + "node_modules/lodash.isnil": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lodash.isnil/-/lodash.isnil-4.0.0.tgz", + "integrity": "sha1-SeKM1VkBNFjIFMVHnTxmOiG/qmw=" + }, + "node_modules/lodash.isundefined": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz", + "integrity": "sha1-I+89lTVWUgOmbO/VuDD4SJEa+0g=" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", + "dev": true + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" + }, + "node_modules/logform": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.3.0.tgz", + "integrity": "sha512-graeoWUH2knKbGthMtuG1EfaSPMZFZBIrhuJHhkS5ZseFBrc7DupCzihOQAzsK/qIKPQaPJ/lFQFctILUY5ARQ==", + "dependencies": { + "colors": "^1.2.1", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^1.1.0", + "triple-beam": "^1.3.0" + } + }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "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-dir/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_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.11", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", + "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", + "dev": true, + "dependencies": { + "tmpl": "1.0.x" + } + }, + "node_modules/marked": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/marked/-/marked-3.0.4.tgz", + "integrity": "sha512-jBo8AOayNaEcvBhNobg6/BLhdsK3NvnKWJg33MAAPbvTWiG4QBn9gpW1+7RssrKu4K1dKlN+0goVQwV41xEfOA==", + "bin": { + "marked": "bin/marked" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "dependencies": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + }, + "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.50.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.50.0.tgz", + "integrity": "sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.32", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", + "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", + "dependencies": { + "mime-db": "1.49.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", + "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/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/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/moment": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", + "engines": { + "node": "*" + } + }, + "node_modules/moment-timezone": { + "version": "0.5.33", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.33.tgz", + "integrity": "sha512-PTc2vcT8K9J5/9rDEPe5czSIKgLoGsH8UNpA4qZTVw0Vd/Uz19geE9abbIOQKaAQFcnQ3v5YEXrbSc5BpshH+w==", + "dependencies": { + "moment": ">= 2.9.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/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/morgan/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/morgan/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/multer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.3.tgz", + "integrity": "sha512-np0YLKncuZoTzufbkM6wEKp68EhWJXcU6fq6QqrSwkckd2LlMgd1UqhUJLj6NS/5sZ8dE8LYDWslsltJznnXlg==", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^0.2.11", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "on-finished": "^2.3.0", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/nanocolors": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/nanocolors/-/nanocolors-0.2.12.tgz", + "integrity": "sha512-SFNdALvzW+rVlzqexid6epYdt8H9Zol7xDoQarioEFcFN0JHo4CYNztAxmtfgGTVRCmFlEOqqhBpoFGKqSAMug==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "node_modules/negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-forge": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", + "dev": true + }, + "node_modules/node-jose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/node-jose/-/node-jose-2.0.0.tgz", + "integrity": "sha512-j8zoFze1gijl8+DK/dSXXqX7+o2lMYv1XS+ptnXgGV/eloQaqq1YjNtieepbKs9jBS4WTnMOqyKSaQuunJzx0A==", + "dependencies": { + "base64url": "^3.0.1", + "buffer": "^5.5.0", + "es6-promise": "^4.2.8", + "lodash": "^4.17.15", + "long": "^4.0.0", + "node-forge": "^0.10.0", + "pako": "^1.0.11", + "process": "^0.11.10", + "uuid": "^3.3.3" + } + }, + "node_modules/node-jose/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/node-modules-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", + "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/node-releases": { + "version": "1.1.76", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.76.tgz", + "integrity": "sha512-9/IECtNr8dXNmPWmFXepT0/7o5eolGesHUa3mtr0KlgnCvnZxwh2qensKL42JJY2vQKC3nIBXetFAqR+PW1CmA==", + "dev": true + }, + "node_modules/nodemon": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.13.tgz", + "integrity": "sha512-UMXMpsZsv1UXUttCn6gv8eQPhn6DR4BW+txnL3IN5IHqrCwcrT/yWHfL35UsClGXknTH79r5xbu+6J1zNHuSyA==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "chokidar": "^3.2.2", + "debug": "^3.2.6", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.0.4", + "pstree.remy": "^1.1.7", + "semver": "^5.7.1", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.3", + "update-notifier": "^5.1.0" + }, + "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": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "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": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "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/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/nwsapi": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", + "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", + "dev": true + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", + "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "node_modules/oidc-token-hash": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.1.tgz", + "integrity": "sha512-EvoOtz6FIEBzE+9q253HsLCVRiK/0doEJ2HCvvqMQb3dHZrP3WlJKYtJ55CRTw4jmYomzH4wkPuCj/I3ZvpKxQ==", + "engines": { + "node": "^10.13.0 || >=12.0.0" + } + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "dependencies": { + "fn.name": "1.x.x" + } + }, + "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/openid-client": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-4.9.0.tgz", + "integrity": "sha512-ThBbvRUUZwxUKBVK2UpDNIZ3eJkvtqWI8s5Dm+naV+gJdL+yRhT+8ywqct1gy5uL+xVS5+A/nhFcpJIisH2x6Q==", + "dependencies": { + "aggregate-error": "^3.1.0", + "got": "^11.8.0", + "jose": "^2.0.5", + "lru-cache": "^6.0.0", + "make-error": "^1.3.6", + "object-hash": "^2.0.1", + "oidc-token-hash": "^5.0.1" + }, + "engines": { + "node": "^10.19.0 || >=12.0.0 < 13 || >=13.7.0 < 14 || >= 14.2.0" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "engines": { + "node": ">=8" + } + }, + "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-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-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/package-json": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", + "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", + "dev": true, + "dependencies": { + "got": "^9.6.0", + "registry-auth-token": "^4.0.0", + "registry-url": "^5.0.0", + "semver": "^6.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/package-json/node_modules/@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json/node_modules/@szmarczak/http-timer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", + "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "dev": true, + "dependencies": { + "defer-to-connect": "^1.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json/node_modules/cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "dev": true, + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^3.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^1.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/package-json/node_modules/cacheable-request/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json/node_modules/decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "dev": true, + "dependencies": { + "mimic-response": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/package-json/node_modules/defer-to-connect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", + "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", + "dev": true + }, + "node_modules/package-json/node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json/node_modules/got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "dev": true, + "dependencies": { + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/package-json/node_modules/got/node_modules/lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/package-json/node_modules/json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", + "dev": true + }, + "node_modules/package-json/node_modules/keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.0" + } + }, + "node_modules/package-json/node_modules/normalize-url": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", + "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/package-json/node_modules/p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json/node_modules/responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "dev": true, + "dependencies": { + "lowercase-keys": "^1.0.0" + } + }, + "node_modules/package-json/node_modules/responselike/node_modules/lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/package-json/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/passport": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.4.1.tgz", + "integrity": "sha512-IxXgZZs8d7uFSt3eqNjM9NQ3g3uQCW5avD8mRNoXV99Yig50vjuaez6dQK2qC0kVWPRTujxY0dWgGfT09adjYg==", + "dependencies": { + "passport-strategy": "1.x.x", + "pause": "0.0.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/passport-custom": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/passport-custom/-/passport-custom-1.1.1.tgz", + "integrity": "sha512-/2m7jUGxmCYvoqenLB9UrmkCgPt64h8ZtV+UtuQklZ/Tn1NpKBeOorCYkB/8lMRoiZ5hUrCoMmDtxCS/d38mlg==", + "dependencies": { + "passport-strategy": "1.x.x" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=", + "engines": { + "node": ">= 0.4.0" + } + }, + "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": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "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": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" + }, + "node_modules/pg": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.7.1.tgz", + "integrity": "sha512-7bdYcv7V6U3KAtWjpQJJBww0UEsWuh4yQ/EjNf2HeO/NnvKjpvhEIe/A/TleP6wtmSKnUnghs5A9jUoK6iDdkA==", + "dependencies": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "^2.5.0", + "pg-pool": "^3.4.1", + "pg-protocol": "^1.5.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "pg-native": ">=2.0.0" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-connection-string": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", + "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-numeric": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pg-numeric/-/pg-numeric-1.0.2.tgz", + "integrity": "sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/pg-pool": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.4.1.tgz", + "integrity": "sha512-TVHxR/gf3MeJRvchgNHxsYsTCHQ+4wm3VIHSS19z8NC0+gioEhq1okDY1sm/TYbfoP6JLFx01s0ShvZ3puP/iQ==", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz", + "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==" + }, + "node_modules/pg-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-3.0.1.tgz", + "integrity": "sha512-Q3zN2GyDEOrc9m6lxCru9JakbdVQY5/ylKxSXI/PcIQPVRDNBR7BxdeCtxa9WI+UIoE3JNcyAffGThOxd+V/4g==", + "dev": true, + "dependencies": { + "pg-int8": "1.0.1", + "pg-numeric": "1.0.2", + "postgres-array": "~2.0.0", + "postgres-bytea": "~3.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pg/node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pg/node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pgpass": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.4.tgz", + "integrity": "sha512-YmuA56alyBq7M59vxVBfPJrGSozru8QAdoNlWuW3cz8l+UX3cWge0vTvjKhsSHSJpo3Bom8/Mm6hf0TR5GY0+w==", + "dependencies": { + "split2": "^3.1.1" + } + }, + "node_modules/picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", + "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", + "dev": true, + "dependencies": { + "node-modules-regexp": "^1.0.0" + }, + "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/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-3.0.0.tgz", + "integrity": "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==", + "dev": true, + "dependencies": { + "obuf": "~1.1.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.1.tgz", + "integrity": "sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-format": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.2.4.tgz", + "integrity": "sha512-NUjw22WJHldzxyps2YjLZkUj6q1HvjqFezkB9Y2cklN8NtVZN/kZEXGZdFw4uny3oENzV5EEMESrkI0YDUH8vg==", + "dev": true, + "dependencies": { + "@jest/types": "^27.2.4", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/prompts": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.1.tgz", + "integrity": "sha512-EQyfIuO2hPDsX1L/blblV+H7I0knhgAd82cVneCwcdND9B8AuCDuRcBH6yIcG4dFzlOUqbazQqwGjx5xmsNLuQ==", + "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/psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "dev": true + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pupa": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", + "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", + "dev": true, + "dependencies": { + "escape-goat": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "dependencies": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + }, + "node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "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/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/registry-auth-token": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", + "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", + "dev": true, + "dependencies": { + "rc": "^1.2.8" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/registry-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", + "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", + "dev": true, + "dependencies": { + "rc": "^1.2.8" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "dependencies": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" + }, + "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-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/responselike": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", + "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", + "dependencies": { + "lowercase-keys": "^2.0.0" + } + }, + "node_modules/retry-as-promised": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-3.2.0.tgz", + "integrity": "sha512-CybGs60B7oYU/qSQ6kuaFmRd9sTZ6oXSc0toqePvV74Ac6/IFZSI1ReFQmtCN+uvW1Mtqdwpvt/LGOiCBAY2Mg==", + "dependencies": { + "any-promise": "^1.3.0" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/safe-stable-stringify": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-1.1.1.tgz", + "integrity": "sha512-ERq4hUjKDbJfE4+XtZLFPCDi8Vb1JqaxAPTxWFLBx8XcAlf9Bda/ZJdVezs/NAfsMQScyIlUMx+Yeu7P7rx5jw==" + }, + "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/saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "dev": true, + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-diff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", + "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", + "dev": true, + "dependencies": { + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/semver-diff/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "dependencies": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/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/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "node_modules/sequelize": { + "version": "6.6.5", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.6.5.tgz", + "integrity": "sha512-QyRrJrDRiwuiILqTMHUA1yWOPIL12KlfmgZ3hnzQwbMvp2vJ6fzu9bYJQB+qPMosck4mBUggY4Cjoc6Et8FBIQ==", + "dependencies": { + "debug": "^4.1.1", + "dottie": "^2.0.0", + "inflection": "1.13.1", + "lodash": "^4.17.20", + "moment": "^2.26.0", + "moment-timezone": "^0.5.31", + "retry-as-promised": "^3.2.0", + "semver": "^7.3.2", + "sequelize-pool": "^6.0.0", + "toposort-class": "^1.0.1", + "uuid": "^8.1.0", + "validator": "^13.6.0", + "wkx": "^0.5.0" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependenciesMeta": { + "mariadb": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "pg": { + "optional": true + }, + "pg-hstore": { + "optional": true + }, + "sqlite3": { + "optional": true + }, + "tedious": { + "optional": true + } + } + }, + "node_modules/sequelize-mock": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/sequelize-mock/-/sequelize-mock-0.10.2.tgz", + "integrity": "sha1-GdOXHM2utbhkFwwkznkqinHxRL0=", + "dev": true, + "dependencies": { + "bluebird": "^3.4.6", + "inflection": "^1.10.0", + "lodash": "^4.16.4" + } + }, + "node_modules/sequelize-pool": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-6.1.0.tgz", + "integrity": "sha512-4YwEw3ZgK/tY/so+GfnSgXkdwIJJ1I32uZJztIEgZeAO6HMgj64OzySbWLgxj+tXhZCJnzRfkY9gINw8Ft8ZMg==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/sequelize/node_modules/validator": { + "version": "13.6.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.6.0.tgz", + "integrity": "sha512-gVgKbdbHgtxpRyR8K0O6oFZPhhB5tT1jeEHZR0Znr9Svg03U0+r9DXWMrnRAB+HtCStDQKlaIZm42tVsVjqtjg==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.5.tgz", + "integrity": "sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==", + "dev": true + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "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/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/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-support": { + "version": "0.5.20", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", + "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/split2": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "dependencies": { + "readable-stream": "^3.0.0" + } + }, + "node_modules/split2/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/split2/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/split2/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", + "engines": { + "node": "*" + } + }, + "node_modules/stack-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", + "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/streamsearch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "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/superagent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-6.1.0.tgz", + "integrity": "sha512-OUDHEssirmplo3F+1HWKUrUjvnQuA+nZI6i/JJBdXb5eq9IyEQwPyPpqND+SSsxf6TygpBEkUjISVRN4/VOpeg==", + "dev": true, + "dependencies": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.2", + "debug": "^4.1.1", + "fast-safe-stringify": "^2.0.7", + "form-data": "^3.0.0", + "formidable": "^1.2.2", + "methods": "^1.1.2", + "mime": "^2.4.6", + "qs": "^6.9.4", + "readable-stream": "^3.6.0", + "semver": "^7.3.2" + }, + "engines": { + "node": ">= 7.0.0" + } + }, + "node_modules/superagent/node_modules/mime": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", + "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/superagent/node_modules/qs": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", + "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/superagent/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/superagent/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/superagent/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/supertest": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.1.6.tgz", + "integrity": "sha512-0hACYGNJ8OHRg8CRITeZOdbjur7NLuNs0mBjVhdpxi7hP6t3QIbOzLON5RTUmZcy2I9riuII3+Pr2C7yztrIIg==", + "dev": true, + "dependencies": { + "methods": "^1.1.2", + "superagent": "^6.1.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "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-hyperlinks": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", + "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "node_modules/table": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/table/-/table-6.7.2.tgz", + "integrity": "sha512-UFZK67uvyNivLeQbVtkiUs8Uuuxv24aSL4/Vil2PJVtMgU8Lx0CYkP12uCGa3kjyQzOSgV1+z9Wkb82fCGsO0g==", + "dev": true, + "dependencies": { + "ajv": "^8.0.1", + "lodash.clonedeep": "^4.5.0", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ajv": { + "version": "8.6.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", + "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "node_modules/throat": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", + "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==", + "dev": true + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-readable-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/toposort-class": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", + "integrity": "sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg=" + }, + "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/tough-cookie": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", + "dev": true, + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.1.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", + "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", + "dev": true, + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/triple-beam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", + "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" + }, + "node_modules/ts-jest": { + "version": "27.0.5", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-27.0.5.tgz", + "integrity": "sha512-lIJApzfTaSSbtlksfFNHkWOzLJuuSm4faFAfo5kvzOiRAuoN4/eKxVJ2zEAho8aecE04qX6K1pAzfH5QHL1/8w==", + "dev": true, + "dependencies": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^27.0.0", + "json5": "2.x", + "lodash": "4.x", + "make-error": "1.x", + "semver": "7.x", + "yargs-parser": "20.x" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@types/jest": "^27.0.0", + "babel-jest": ">=27.0.0 <28", + "jest": "^27.0.0", + "typescript": ">=3.8 <5.0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@types/jest": { + "optional": true + }, + "babel-jest": { + "optional": true + } + } + }, + "node_modules/ts-node": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.2.1.tgz", + "integrity": "sha512-hCnyOyuGmD5wHleOQX6NIjJtYVIO8bPP8F2acWkB4W06wdlkgyvJtubO/I9NkI88hCFECbsEgoLc0VNkYmcSfw==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "0.6.1", + "@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", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "engines": { + "node": ">=12.0.0" + }, + "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/ts-node/node_modules/acorn": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", + "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ts-node/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==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-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.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typescript": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.3.tgz", + "integrity": "sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "dependencies": { + "random-bytes": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/undefsafe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", + "integrity": "sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==", + "dev": true, + "dependencies": { + "debug": "^2.2.0" + } + }, + "node_modules/undefsafe/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/undefsafe/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dev": true, + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-notifier": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz", + "integrity": "sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==", + "dev": true, + "dependencies": { + "boxen": "^5.0.0", + "chalk": "^4.1.0", + "configstore": "^5.0.1", + "has-yarn": "^2.1.0", + "import-lazy": "^2.1.0", + "is-ci": "^2.0.0", + "is-installed-globally": "^0.4.0", + "is-npm": "^5.0.0", + "is-yarn-global": "^0.3.0", + "latest-version": "^5.1.0", + "pupa": "^2.1.1", + "semver": "^7.3.4", + "semver-diff": "^3.1.1", + "xdg-basedir": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/yeoman/update-notifier?sponsor=1" + } + }, + "node_modules/update-notifier/node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "node_modules/update-notifier/node_modules/is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "dependencies": { + "ci-info": "^2.0.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", + "dev": true, + "dependencies": { + "prepend-http": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "node_modules/v8-to-istanbul": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.0.tgz", + "integrity": "sha512-/PRhfd8aTNp9Ggr62HPzXg2XasNFGy5PBt0Rp04du7/8GNNSgxFL6WBTkgMKSL9bFjH+8kKEG3f37FmxiTqUUA==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0", + "source-map": "^0.7.3" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/v8-to-istanbul/node_modules/source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/validator": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.0.0.tgz", + "integrity": "sha512-anYx5fURbgF04lQV18nEQWZ/3wHGnxiKdG4aL8J+jEDsm98n/sU/bey+tYk6tnGJzm7ioh5FoqrAiQ6m03IgaA==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "dev": true, + "dependencies": { + "browser-process-hrtime": "^1.0.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", + "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "dev": true, + "dependencies": { + "xml-name-validator": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/walker": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", + "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", + "dev": true, + "dependencies": { + "makeerror": "1.0.x" + } + }, + "node_modules/webidl-conversions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "dev": true, + "engines": { + "node": ">=10.4" + } + }, + "node_modules/whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dev": true, + "dependencies": { + "iconv-lite": "0.4.24" + } + }, + "node_modules/whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "dev": true + }, + "node_modules/whatwg-url": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", + "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", + "dev": true, + "dependencies": { + "lodash": "^4.7.0", + "tr46": "^2.1.0", + "webidl-conversions": "^6.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "dev": true, + "dependencies": { + "string-width": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/winston": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.3.3.tgz", + "integrity": "sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw==", + "dependencies": { + "@dabh/diagnostics": "^2.0.2", + "async": "^3.1.0", + "is-stream": "^2.0.0", + "logform": "^2.2.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.4.0" + }, + "engines": { + "node": ">= 6.4.0" + } + }, + "node_modules/winston-daily-rotate-file": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-4.5.5.tgz", + "integrity": "sha512-ds0WahIjiDhKCiMXmY799pDBW+58ByqIBtUcsqr4oDoXrAI3Zn+hbgFdUxzMfqA93OG0mPLYVMiotqTgE/WeWQ==", + "dependencies": { + "file-stream-rotator": "^0.5.7", + "object-hash": "^2.0.1", + "triple-beam": "^1.3.0", + "winston-transport": "^4.4.0" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "winston": "^3" + } + }, + "node_modules/winston-transport": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.4.0.tgz", + "integrity": "sha512-Lc7/p3GtqtqPBYYtS6KCN3c77/2QCev51DvcJKbkFPQNoj1sinkGwLGFDxkXY9J6p9+EPnYs+D90uwbnaiURTw==", + "dependencies": { + "readable-stream": "^2.3.7", + "triple-beam": "^1.2.0" + }, + "engines": { + "node": ">= 6.4.0" + } + }, + "node_modules/winston-transport/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "node_modules/winston-transport/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/winston-transport/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/winston/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/winston/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/winston/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/wkx": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz", + "integrity": "sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/ws": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz", + "integrity": "sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==", + "dev": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/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": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + } + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/compat-data": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.15.0.tgz", + "integrity": "sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA==", + "dev": true + }, + "@babel/core": { + "version": "7.15.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.15.5.tgz", + "integrity": "sha512-pYgXxiwAgQpgM1bNkZsDEq85f0ggXMA5L7c+o3tskGMh2BunCI9QUwB9Z4jpvXUOuMdyGKiGKQiRe11VS6Jzvg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.15.4", + "@babel/helper-compilation-targets": "^7.15.4", + "@babel/helper-module-transforms": "^7.15.4", + "@babel/helpers": "^7.15.4", + "@babel/parser": "^7.15.5", + "@babel/template": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.4", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.1.2", + "semver": "^6.3.0", + "source-map": "^0.5.0" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.14.5" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.4.tgz", + "integrity": "sha512-d3itta0tu+UayjEORPNz6e1T3FtvWlP5N4V5M+lhp/CxT4oAA7/NcScnpRyspUMLK6tu9MNHmQHxRykuN2R7hw==", + "dev": true, + "requires": { + "@babel/types": "^7.15.4", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/helper-compilation-targets": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.4.tgz", + "integrity": "sha512-rMWPCirulnPSe4d+gwdWXLfAXTTBj8M3guAf5xFQJ0nvFY7tfNAFnWdqaHegHlgDZOCT4qvhF3BYlSJag8yhqQ==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.15.0", + "@babel/helper-validator-option": "^7.14.5", + "browserslist": "^4.16.6", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/helper-function-name": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz", + "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.15.4", + "@babel/template": "^7.15.4", + "@babel/types": "^7.15.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz", + "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", + "dev": true, + "requires": { + "@babel/types": "^7.15.4" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.15.4.tgz", + "integrity": "sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA==", + "dev": true, + "requires": { + "@babel/types": "^7.15.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.4.tgz", + "integrity": "sha512-cokOMkxC/BTyNP1AlY25HuBWM32iCEsLPI4BHDpJCHHm1FU2E7dKWWIXJgQgSFiu4lp8q3bL1BIKwqkSUviqtA==", + "dev": true, + "requires": { + "@babel/types": "^7.15.4" + } + }, + "@babel/helper-module-imports": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.15.4.tgz", + "integrity": "sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA==", + "dev": true, + "requires": { + "@babel/types": "^7.15.4" + } + }, + "@babel/helper-module-transforms": { + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.7.tgz", + "integrity": "sha512-ZNqjjQG/AuFfekFTY+7nY4RgBSklgTu970c7Rj3m/JOhIu5KPBUuTA9AY6zaKcUvk4g6EbDXdBnhi35FAssdSw==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.15.4", + "@babel/helper-replace-supers": "^7.15.4", + "@babel/helper-simple-access": "^7.15.4", + "@babel/helper-split-export-declaration": "^7.15.4", + "@babel/helper-validator-identifier": "^7.15.7", + "@babel/template": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.6" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.15.4.tgz", + "integrity": "sha512-E/z9rfbAOt1vDW1DR7k4SzhzotVV5+qMciWV6LaG1g4jeFrkDlJedjtV4h0i4Q/ITnUu+Pk08M7fczsB9GXBDw==", + "dev": true, + "requires": { + "@babel/types": "^7.15.4" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", + "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", + "dev": true + }, + "@babel/helper-replace-supers": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.15.4.tgz", + "integrity": "sha512-/ztT6khaXF37MS47fufrKvIsiQkx1LBRvSJNzRqmbyeZnTwU9qBxXYLaaT/6KaxfKhjs2Wy8kG8ZdsFUuWBjzw==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.15.4", + "@babel/helper-optimise-call-expression": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.4" + } + }, + "@babel/helper-simple-access": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.15.4.tgz", + "integrity": "sha512-UzazrDoIVOZZcTeHHEPYrr1MvTR/K+wgLg6MY6e1CJyaRhbibftF6fR2KU2sFRtI/nERUZR9fBd6aKgBlIBaPg==", + "dev": true, + "requires": { + "@babel/types": "^7.15.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz", + "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", + "dev": true, + "requires": { + "@babel/types": "^7.15.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", + "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", + "dev": true + }, + "@babel/helpers": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.15.4.tgz", + "integrity": "sha512-V45u6dqEJ3w2rlryYYXf6i9rQ5YMNu4FLS6ngs8ikblhu2VdR1AqAd6aJjBzmf2Qzh6KOLqKHxEN9+TFbAkAVQ==", + "dev": true, + "requires": { + "@babel/template": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.4" + } + }, + "@babel/highlight": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/parser": { + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.7.tgz", + "integrity": "sha512-rycZXvQ+xS9QyIcJ9HXeDWf1uxqlbVFAUq0Rq0dbc50Zb/+wUe/ehyfzGfm9KZZF0kBejYgxltBXocP+gKdL2g==", + "dev": true + }, + "@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, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@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, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@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, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@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, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@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, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@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, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@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, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@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, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@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, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@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, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@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, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@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, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-typescript": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.14.5.tgz", + "integrity": "sha512-u6OXzDaIXjEstBRRoBCQ/uKQKlbuaeE5in0RvWdA4pN6AhqxTIwUsnHPU1CFZA/amYObMsuWhYfRl3Ch90HD0Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/template": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", + "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/parser": "^7.15.4", + "@babel/types": "^7.15.4" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.14.5" + } + } + } + }, + "@babel/traverse": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.4.tgz", + "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.15.4", + "@babel/helper-function-name": "^7.15.4", + "@babel/helper-hoist-variables": "^7.15.4", + "@babel/helper-split-export-declaration": "^7.15.4", + "@babel/parser": "^7.15.4", + "@babel/types": "^7.15.4", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.14.5" + } + }, + "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 + } + } + }, + "@babel/types": { + "version": "7.15.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", + "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.9", + "to-fast-properties": "^2.0.0" + } + }, + "@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 + }, + "@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "dev": true + }, + "@cspotcode/source-map-support": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.6.1.tgz", + "integrity": "sha512-DX3Z+T5dt1ockmPdobJS/FAsQPW4V4SrWEhD2iYQT2Cb2tQsiMnYxrcUH9By/Z3B+v0S5LMBkQtV/XOBbpLEOg==", + "dev": true, + "requires": { + "@cspotcode/source-map-consumer": "0.8.0" + } + }, + "@dabh/diagnostics": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.2.tgz", + "integrity": "sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q==", + "requires": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "@eslint/eslintrc": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", + "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + } + } + }, + "@fast-csv/format": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@fast-csv/format/-/format-4.3.5.tgz", + "integrity": "sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==", + "requires": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isboolean": "^3.0.3", + "lodash.isequal": "^4.5.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0" + } + }, + "@fast-csv/parse": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@fast-csv/parse/-/parse-4.3.6.tgz", + "integrity": "sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==", + "requires": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.groupby": "^4.6.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0", + "lodash.isundefined": "^3.0.1", + "lodash.uniq": "^4.5.0" + } + }, + "@humanwhocodes/config-array": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + } + }, + "@humanwhocodes/object-schema": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", + "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", + "dev": true + }, + "@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, + "requires": { + "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" + }, + "dependencies": { + "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 + } + } + }, + "@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 + }, + "@jest/console": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.2.4.tgz", + "integrity": "sha512-94znCKynPZpDpYHQ6esRJSc11AmONrVkBOBZiD7S+bSubHhrUfbS95EY5HIOxhm4PQO7cnvZkL3oJcY0oMA+Wg==", + "dev": true, + "requires": { + "@jest/types": "^27.2.4", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^27.2.4", + "jest-util": "^27.2.4", + "slash": "^3.0.0" + } + }, + "@jest/core": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.2.4.tgz", + "integrity": "sha512-UNQLyy+rXoojNm2MGlapgzWhZD1CT1zcHZQYeiD0xE7MtJfC19Q6J5D/Lm2l7i4V97T30usKDoEtjI8vKwWcLg==", + "dev": true, + "requires": { + "@jest/console": "^27.2.4", + "@jest/reporters": "^27.2.4", + "@jest/test-result": "^27.2.4", + "@jest/transform": "^27.2.4", + "@jest/types": "^27.2.4", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.8.1", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "jest-changed-files": "^27.2.4", + "jest-config": "^27.2.4", + "jest-haste-map": "^27.2.4", + "jest-message-util": "^27.2.4", + "jest-regex-util": "^27.0.6", + "jest-resolve": "^27.2.4", + "jest-resolve-dependencies": "^27.2.4", + "jest-runner": "^27.2.4", + "jest-runtime": "^27.2.4", + "jest-snapshot": "^27.2.4", + "jest-util": "^27.2.4", + "jest-validate": "^27.2.4", + "jest-watcher": "^27.2.4", + "micromatch": "^4.0.4", + "rimraf": "^3.0.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "@jest/environment": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.2.4.tgz", + "integrity": "sha512-wkuui5yr3SSQW0XD0Qm3TATUbL/WE3LDEM3ulC+RCQhMf2yxhci8x7svGkZ4ivJ6Pc94oOzpZ6cdHBAMSYd1ew==", + "dev": true, + "requires": { + "@jest/fake-timers": "^27.2.4", + "@jest/types": "^27.2.4", + "@types/node": "*", + "jest-mock": "^27.2.4" + } + }, + "@jest/fake-timers": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.2.4.tgz", + "integrity": "sha512-cs/TzvwWUM7kAA6Qm/890SK6JJ2pD5RfDNM3SSEom6BmdyV6OiWP1qf/pqo6ts6xwpcM36oN0wSEzcZWc6/B6w==", + "dev": true, + "requires": { + "@jest/types": "^27.2.4", + "@sinonjs/fake-timers": "^8.0.1", + "@types/node": "*", + "jest-message-util": "^27.2.4", + "jest-mock": "^27.2.4", + "jest-util": "^27.2.4" + } + }, + "@jest/globals": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.2.4.tgz", + "integrity": "sha512-DRsRs5dh0i+fA9mGHylTU19+8fhzNJoEzrgsu+zgJoZth3x8/0juCQ8nVVdW1er4Cqifb/ET7/hACYVPD0dBEA==", + "dev": true, + "requires": { + "@jest/environment": "^27.2.4", + "@jest/types": "^27.2.4", + "expect": "^27.2.4" + } + }, + "@jest/reporters": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.2.4.tgz", + "integrity": "sha512-LHeSdDnDZkDnJ8kvnjcqV8P1Yv/32yL4d4XfR5gBiy3xGO0onwll1QEbvtW96fIwhx2nejug0GTaEdNDoyr3fQ==", + "dev": true, + "requires": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^27.2.4", + "@jest/test-result": "^27.2.4", + "@jest/transform": "^27.2.4", + "@jest/types": "^27.2.4", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.2", + "graceful-fs": "^4.2.4", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^4.0.3", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "jest-haste-map": "^27.2.4", + "jest-resolve": "^27.2.4", + "jest-util": "^27.2.4", + "jest-worker": "^27.2.4", + "slash": "^3.0.0", + "source-map": "^0.6.0", + "string-length": "^4.0.1", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^8.1.0" + } + }, + "@jest/source-map": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.0.6.tgz", + "integrity": "sha512-Fek4mi5KQrqmlY07T23JRi0e7Z9bXTOOD86V/uS0EIW4PClvPDqZOyFlLpNJheS6QI0FNX1CgmPjtJ4EA/2M+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0", + "graceful-fs": "^4.2.4", + "source-map": "^0.6.0" + } + }, + "@jest/test-result": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.2.4.tgz", + "integrity": "sha512-eU+PRo0+lIS01b0dTmMdVZ0TtcRSxEaYquZTRFMQz6CvsehGhx9bRzi9Zdw6VROviJyv7rstU+qAMX5pNBmnfQ==", + "dev": true, + "requires": { + "@jest/console": "^27.2.4", + "@jest/types": "^27.2.4", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + } + }, + "@jest/test-sequencer": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.2.4.tgz", + "integrity": "sha512-fpk5eknU3/DXE2QCCG1wv/a468+cfPo3Asu6d6yUtM9LOPh709ubZqrhuUOYfM8hXMrIpIdrv1CdCrWWabX0rQ==", + "dev": true, + "requires": { + "@jest/test-result": "^27.2.4", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^27.2.4", + "jest-runtime": "^27.2.4" + } + }, + "@jest/transform": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.2.4.tgz", + "integrity": "sha512-n5FlX2TH0oQGwyVDKPxdJ5nI2sO7TJBFe3u3KaAtt7TOiV4yL+Y+rSFDl+Ic5MpbiA/eqXmLAQxjnBmWgS2rEA==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/types": "^27.2.4", + "babel-plugin-istanbul": "^6.0.0", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^27.2.4", + "jest-regex-util": "^27.0.6", + "jest-util": "^27.2.4", + "micromatch": "^4.0.4", + "pirates": "^4.0.1", + "slash": "^3.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "^3.0.0" + } + }, + "@jest/types": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.2.4.tgz", + "integrity": "sha512-IDO2ezTxeMvQAHxzG/ZvEyA47q0aVfzT95rGFl7bZs/Go0aIucvfDbS2rmnoEdXxlLQhcolmoG/wvL/uKx4tKA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@panva/asn1.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", + "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==" + }, + "@sindresorhus/is": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.2.0.tgz", + "integrity": "sha512-VkE3KLBmJwcCaVARtQpfuKcKv8gcBmUubrfHGF84dXuuW6jgsRYxPtzcIhPyK9WAPpRt2/xY6zkD9MnRaJzSyw==" + }, + "@sinonjs/commons": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.0.1.tgz", + "integrity": "sha512-AU7kwFxreVd6OAXcAFlKSmZquiRUU0FvYm44k1Y1QbK7Co4m0aqfGMhjykIeQp/H6rcl+nFmj0zfdUcGVs9Dew==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "requires": { + "defer-to-connect": "^2.0.0" + } + }, + "@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "dev": true + }, + "@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true + }, + "@types/babel__core": { + "version": "7.1.16", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.16.tgz", + "integrity": "sha512-EAEHtisTMM+KaKwfWdC3oyllIqswlznXCIVCt7/oRNrh+DhgT4UEBNC/jlADNjvw7UnfbcdkGQcPVZ1xYiLcrQ==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.3.tgz", + "integrity": "sha512-/GWCmzJWqV7diQW54smJZzWbSFf4QYtF71WCKhcx6Ru/tFyQIY2eiiITcCAeuPbNSvT9YCGkVMqqvSk2Z0mXiA==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@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, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.14.2", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.14.2.tgz", + "integrity": "sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA==", + "dev": true, + "requires": { + "@babel/types": "^7.3.0" + } + }, + "@types/bluebird": { + "version": "3.5.36", + "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.36.tgz", + "integrity": "sha512-HBNx4lhkxN7bx6P0++W8E289foSu8kO8GCk2unhuVggO+cE7rh9DhZUyPhUxNRG9m+5B5BTKxZQ5ZP92x/mx9Q==", + "dev": true + }, + "@types/body-parser": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.1.tgz", + "integrity": "sha512-a6bTJ21vFOGIkwM0kzh9Yr89ziVxq4vYH2fQ6N8AeipEzai/cFK6aGMArIkUeIdRIgpwQa+2bXiLuUJCpSf2Cg==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/cacheable-request": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", + "integrity": "sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==", + "requires": { + "@types/http-cache-semantics": "*", + "@types/keyv": "*", + "@types/node": "*", + "@types/responselike": "*" + } + }, + "@types/compression": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.7.2.tgz", + "integrity": "sha512-lwEL4M/uAGWngWFLSG87ZDr2kLrbuR8p7X+QZB1OQlT+qkHsCPDVFnHPyXf4Vyl4yDDorNY+mAhosxkCvppatg==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/connect-pg-simple": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@types/connect-pg-simple/-/connect-pg-simple-4.2.4.tgz", + "integrity": "sha512-WzpKF3utq5iWK5k9ZSQ9edR8Y/HUhq4Nrz4cFSXdo6EJgv13RfLMbhDQCf8Xef4JbyhYmJSUovXmAp2oQzvj3w==", + "dev": true, + "requires": { + "@types/express": "*", + "@types/express-session": "*", + "@types/pg": "*" + } + }, + "@types/continuation-local-storage": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@types/continuation-local-storage/-/continuation-local-storage-3.2.3.tgz", + "integrity": "sha512-4LYeWblV+6puK9tFGM7Zr4OLZkVXmaL7hUK6/wHwbfwM+q7v+HZyBWTXkNOiC9GqOxv7ehhi5TMCbebZWeVYtw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/cookie-parser": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.2.tgz", + "integrity": "sha512-uwcY8m6SDQqciHsqcKDGbo10GdasYsPCYkH3hVegj9qAah6pX5HivOnOuI3WYmyQMnOATV39zv/Ybs0bC/6iVg==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==", + "dev": true + }, + "@types/cors": { + "version": "2.8.12", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", + "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==", + "dev": true + }, + "@types/events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", + "dev": true + }, + "@types/express": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", + "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.24", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.24.tgz", + "integrity": "sha512-3UJuW+Qxhzwjq3xhwXm2onQcFHn76frIYVbTu+kn24LFxI+dEhdfISDFovPB8VpEgW8oQCTpRuCe+0zJxB7NEA==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "@types/express-session": { + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.4.tgz", + "integrity": "sha512-7cNlSI8+oOBUHTfPXMwDxF/Lchx5aJ3ho7+p9jJZYVg9dVDJFh3qdMXmJtRsysnvS+C6x46k9DRYmrmCkE+MVg==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/graceful-fs": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", + "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/hpp": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@types/hpp/-/hpp-0.2.2.tgz", + "integrity": "sha512-BLgsawqFFbS3tFUr+mcBRfst+DumnSfi4PgyNeJAGk0eIxm7lKX1axmHVlbgKNAZS0caZA5/LSopuj0T2LKRPw==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/http-cache-semantics": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", + "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", + "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", + "dev": true + }, + "@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, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@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, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/jest": { + "version": "27.0.2", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.0.2.tgz", + "integrity": "sha512-4dRxkS/AFX0c5XW6IPMNOydLn2tEhNhJV7DnYK+0bjoJZ+QTmfucBlihX7aoEsh/ocYtkLC73UbnBXBXIxsULA==", + "dev": true, + "requires": { + "jest-diff": "^27.0.0", + "pretty-format": "^27.0.0" + } + }, + "@types/json-schema": { + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", + "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", + "dev": true + }, + "@types/keyv": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.3.tgz", + "integrity": "sha512-FXCJgyyN3ivVgRoml4h94G/p3kY+u/B86La+QptcqJaWtBWtmc6TtkNfS40n9bIvyLteHh7zXOtgbobORKPbDg==", + "requires": { + "@types/node": "*" + } + }, + "@types/lodash": { + "version": "4.14.175", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.175.tgz", + "integrity": "sha512-XmdEOrKQ8a1Y/yxQFOMbC47G/V2VDO1GvMRnl4O75M4GW/abC5tnfzadQYkqEveqRM1dEJGFFegfPNA2vvx2iw==", + "dev": true + }, + "@types/marked": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/marked/-/marked-3.0.1.tgz", + "integrity": "sha512-jry/WUAC511P2NBCeiCkfTRCN2VXobeeQa8p8gImOYsRfnuIVfeEsqOJ1pk+CzCwfMCdv3dkTQRCYaNkkFGtxw==", + "dev": true + }, + "@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", + "dev": true + }, + "@types/morgan": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.3.tgz", + "integrity": "sha512-BiLcfVqGBZCyNCnCH3F4o2GmDLrpy0HeBVnNlyZG4fo88ZiE9SoiBe3C+2ezuwbjlEyT+PDZ17//TAlRxAn75Q==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/multer": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.7.tgz", + "integrity": "sha512-/SNsDidUFCvqqcWDwxv2feww/yqhNeTRL5CVoL3jU4Goc4kKEL10T7Eye65ZqPNi4HRx8sAEX59pV1aEH7drNA==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/node": { + "version": "14.17.20", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.20.tgz", + "integrity": "sha512-gI5Sl30tmhXsqkNvopFydP7ASc4c2cLfGNQrVKN3X90ADFWFsPEsotm/8JHSUJQKTHbwowAHtcJPeyVhtKv0TQ==" + }, + "@types/node-jose": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@types/node-jose/-/node-jose-1.1.8.tgz", + "integrity": "sha512-AFcArbplUaO+DqGVEPaiz/guw3uUA+dRHjaj26EEDF0DmTEPUd3dEdfdJMUx4kD65EAR3TnI1iHIcb31+Ko87Q==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/passport": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.7.tgz", + "integrity": "sha512-JtswU8N3kxBYgo+n9of7C97YQBT+AYPP2aBfNGTzABqPAZnK/WOAaKfh3XesUYMZRrXFuoPc2Hv0/G/nQFveHw==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/pg": { + "version": "7.4.8", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-7.4.8.tgz", + "integrity": "sha512-naSYLMOIBU+/BddUlQUyQuba9reG5yN0MR7g4EcN/AEt3zll48fGozX8s7xjaXuKmFOS1qqOrr/ZeF6SSOjKCw==", + "dev": true, + "requires": { + "@types/events": "*", + "@types/node": "*", + "@types/pg-types": "*" + } + }, + "@types/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-a+fLdul8OczRvPWPf8eTb6wPhxzyWQwRGhNN0ugtOtk6yFOG53i2LwXaA0d2D6bsJlWxi6eCuGZLGoCcdOlWZA==", + "dev": true, + "requires": { + "pg-types": "*" + } + }, + "@types/prettier": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.1.tgz", + "integrity": "sha512-Fo79ojj3vdEZOHg3wR9ksAMRz4P3S5fDB5e/YWZiFnyFQI1WY2Vftu9XoXVVtJfxB7Bpce/QTqWSSntkz2Znrw==", + "dev": true + }, + "@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 + }, + "@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 + }, + "@types/responselike": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", + "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "requires": { + "@types/node": "*" + } + }, + "@types/sequelize": { + "version": "4.28.10", + "resolved": "https://registry.npmjs.org/@types/sequelize/-/sequelize-4.28.10.tgz", + "integrity": "sha512-GKbEbl6uyEYTPvU2JZvmqZHfpwTTjaZvNSd2gFJrhcxUL1bcyG7i+S8Od2L0/+skrk2bBINl7J1Sugo0mgIY3g==", + "dev": true, + "requires": { + "@types/bluebird": "*", + "@types/continuation-local-storage": "*", + "@types/lodash": "*", + "@types/validator": "*" + } + }, + "@types/serve-static": { + "version": "1.13.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", + "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", + "dev": true, + "requires": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "@types/showdown": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/showdown/-/showdown-1.9.4.tgz", + "integrity": "sha512-50ehC3IAijfkvoNqmQ+VL73S7orOxmAK8ljQAFBv8o7G66lAZyxQj1L3BAv2dD86myLXI+sgKP1kcxAaxW356w==", + "dev": true + }, + "@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "@types/superagent": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-4.1.13.tgz", + "integrity": "sha512-YIGelp3ZyMiH0/A09PMAORO0EBGlF5xIKfDpK74wdYvWUs2o96b5CItJcWPdH409b7SAXIIG6p8NdU/4U2Maww==", + "dev": true, + "requires": { + "@types/cookiejar": "*", + "@types/node": "*" + } + }, + "@types/supertest": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.11.tgz", + "integrity": "sha512-uci4Esokrw9qGb9bvhhSVEjd6rkny/dk5PK/Qz4yxKiyppEI+dOPlNrZBahE3i+PoKFYyDxChVXZ/ysS/nrm1Q==", + "dev": true, + "requires": { + "@types/superagent": "*" + } + }, + "@types/uuid": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.1.tgz", + "integrity": "sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg==", + "dev": true + }, + "@types/validator": { + "version": "13.6.3", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.6.3.tgz", + "integrity": "sha512-fWG42pMJOL4jKsDDZZREnXLjc3UE0R8LOJfARWYg6U966rxDT7TYejYzLnUF5cvSObGg34nd0+H2wHHU5Omdfw==", + "dev": true + }, + "@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", + "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", + "dev": true + }, + "@typescript-eslint/eslint-plugin": { + "version": "4.32.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.32.0.tgz", + "integrity": "sha512-+OWTuWRSbWI1KDK8iEyG/6uK2rTm3kpS38wuVifGUTDB6kjEuNrzBI1MUtxnkneuWG/23QehABe2zHHrj+4yuA==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "4.32.0", + "@typescript-eslint/scope-manager": "4.32.0", + "debug": "^4.3.1", + "functional-red-black-tree": "^1.0.1", + "ignore": "^5.1.8", + "regexpp": "^3.1.0", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/experimental-utils": { + "version": "4.32.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.32.0.tgz", + "integrity": "sha512-WLoXcc+cQufxRYjTWr4kFt0DyEv6hDgSaFqYhIzQZ05cF+kXfqXdUh+//kgquPJVUBbL3oQGKQxwPbLxHRqm6A==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.7", + "@typescript-eslint/scope-manager": "4.32.0", + "@typescript-eslint/types": "4.32.0", + "@typescript-eslint/typescript-estree": "4.32.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + } + }, + "@typescript-eslint/parser": { + "version": "4.32.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.32.0.tgz", + "integrity": "sha512-lhtYqQ2iEPV5JqV7K+uOVlPePjClj4dOw7K4/Z1F2yvjIUvyr13yJnDzkK6uon4BjHYuHy3EG0c2Z9jEhFk56w==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "4.32.0", + "@typescript-eslint/types": "4.32.0", + "@typescript-eslint/typescript-estree": "4.32.0", + "debug": "^4.3.1" + } + }, + "@typescript-eslint/scope-manager": { + "version": "4.32.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.32.0.tgz", + "integrity": "sha512-DK+fMSHdM216C0OM/KR1lHXjP1CNtVIhJ54kQxfOE6x8UGFAjha8cXgDMBEIYS2XCYjjCtvTkjQYwL3uvGOo0w==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.32.0", + "@typescript-eslint/visitor-keys": "4.32.0" + } + }, + "@typescript-eslint/types": { + "version": "4.32.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.32.0.tgz", + "integrity": "sha512-LE7Z7BAv0E2UvqzogssGf1x7GPpUalgG07nGCBYb1oK4mFsOiFC/VrSMKbZQzFJdN2JL5XYmsx7C7FX9p9ns0w==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "4.32.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.32.0.tgz", + "integrity": "sha512-tRYCgJ3g1UjMw1cGG8Yn1KzOzNlQ6u1h9AmEtPhb5V5a1TmiHWcRyF/Ic+91M4f43QeChyYlVTcf3DvDTZR9vw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.32.0", + "@typescript-eslint/visitor-keys": "4.32.0", + "debug": "^4.3.1", + "globby": "^11.0.3", + "is-glob": "^4.0.1", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "4.32.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.32.0.tgz", + "integrity": "sha512-e7NE0qz8W+atzv3Cy9qaQ7BTLwWsm084Z0c4nIO2l3Bp6u9WIgdqCgyPyV5oSPDMIW3b20H59OOCmVk3jw3Ptw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.32.0", + "eslint-visitor-keys": "^2.0.0" + } + }, + "abab": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", + "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", + "dev": true + }, + "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 + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + } + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dev": true, + "requires": { + "string-width": "^4.1.0" + } + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + }, + "dependencies": { + "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 + } + } + }, + "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 + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY=" + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, + "async": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.1.tgz", + "integrity": "sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "babel-jest": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.2.4.tgz", + "integrity": "sha512-f24OmxyWymk5jfgLdlCMu4fTs4ldxFBIdn5sJdhvGC1m08rSkJ5hYbWkNmfBSvE/DjhCVNSHXepxsI6THGfGsg==", + "dev": true, + "requires": { + "@jest/transform": "^27.2.4", + "@jest/types": "^27.2.4", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.0.0", + "babel-preset-jest": "^27.2.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "slash": "^3.0.0" + } + }, + "babel-plugin-istanbul": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", + "integrity": "sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^4.0.0", + "test-exclude": "^6.0.0" + } + }, + "babel-plugin-jest-hoist": { + "version": "27.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.2.0.tgz", + "integrity": "sha512-TOux9khNKdi64mW+0OIhcmbAn75tTlzKhxmiNXevQaPbrBYK7YKjP1jl6NHTJ6XR5UgUrJbCnWlKVnJn29dfjw==", + "dev": true, + "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.0.0", + "@types/babel__traverse": "^7.0.6" + } + }, + "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, + "requires": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-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" + } + }, + "babel-preset-jest": { + "version": "27.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.2.0.tgz", + "integrity": "sha512-z7MgQ3peBwN5L5aCqBKnF6iqdlvZvFUQynEhu0J+X9nHLU72jO3iY331lcYrg+AssJ8q7xsv5/3AICzVmJ/wvg==", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^27.2.0", + "babel-preset-current-node-syntax": "^1.0.0" + } + }, + "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 + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==" + }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "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 + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + }, + "dependencies": { + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "boxen": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", + "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", + "dev": true, + "requires": { + "ansi-align": "^3.0.0", + "camelcase": "^6.2.0", + "chalk": "^4.1.0", + "cli-boxes": "^2.2.1", + "string-width": "^4.2.2", + "type-fest": "^0.20.2", + "widest-line": "^3.1.0", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true + } + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true + }, + "browserslist": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.17.2.tgz", + "integrity": "sha512-jSDZyqJmkKMEMi7SZAgX5UltFdR5NAO43vY0AwTpu4X3sGH7GLLQ83KiUomgrnvZRCeW0yPPnKqnxPqQOER9zQ==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001261", + "electron-to-chromium": "^1.3.854", + "escalade": "^3.1.1", + "nanocolors": "^0.2.12", + "node-releases": "^1.1.76" + } + }, + "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, + "requires": { + "fast-json-stable-stringify": "2.x" + } + }, + "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, + "requires": { + "node-int64": "^0.4.0" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "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==" + }, + "buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" + }, + "busboy": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", + "integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=", + "requires": { + "dicer": "0.2.5", + "readable-stream": "1.1.x" + } + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + }, + "cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==" + }, + "cacheable-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", + "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "dependencies": { + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "requires": { + "pump": "^3.0.0" + } + } + } + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001261", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001261.tgz", + "integrity": "sha512-vM8D9Uvp7bHIN0fZ2KQ4wnmYFpJo/Etb4Vwsuc+ka0tfGDHvOPrFm6S/7CCNLSOkAUjenT2HnUPESdOIL91FaA==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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 + }, + "chokidar": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "ci-info": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.2.0.tgz", + "integrity": "sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A==", + "dev": true + }, + "cjs-module-lexer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", + "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "dev": true + }, + "class-transformer": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.3.1.tgz", + "integrity": "sha512-cKFwohpJbuMovS8xVLmn8N2AUbAuc8pVo4zEfsUVo8qgECOogns1WVk/FkOZoxhOPTyTYFckuoH+13FO+MQ8GA==" + }, + "class-validator": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.12.2.tgz", + "integrity": "sha512-TDzPzp8BmpsbPhQpccB3jMUE/3pK0TyqamrK0kcx+ZeFytMA+O6q87JZZGObHHnoo9GM8vl/JppIyKWeEA/EVw==", + "requires": { + "@types/validator": "13.0.0", + "google-libphonenumber": "^3.2.8", + "tslib": ">=1.9.0", + "validator": "13.0.0" + }, + "dependencies": { + "@types/validator": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.0.0.tgz", + "integrity": "sha512-WAy5txG7aFX8Vw3sloEKp5p/t/Xt8jD3GRD9DacnFv6Vo8ubudAsRTXgxpQwU0mpzY/H8U4db3roDuCMjShBmw==" + } + } + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==" + }, + "cli-boxes": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "dev": true + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "requires": { + "mimic-response": "^1.0.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true + }, + "color": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", + "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", + "requires": { + "color-convert": "^1.9.1", + "color-string": "^1.5.2" + }, + "dependencies": { + "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==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + } + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "color-string": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.6.0.tgz", + "integrity": "sha512-c/hGS+kRWJutUBEngKKmk4iH3sD59MBkoxVapS/0wgpCz2u7XsNloxknyvBhzwEs1IbV36D9PwqLPJ2DTu3vMA==", + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" + }, + "colorspace": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz", + "integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==", + "requires": { + "color": "3.0.x", + "text-hex": "1.0.x" + } + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "requires": { + "mime-db": ">= 1.43.0 < 2" + } + }, + "compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "requires": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "dev": true, + "requires": { + "dot-prop": "^5.2.0", + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" + } + }, + "connect-pg-simple": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/connect-pg-simple/-/connect-pg-simple-6.2.1.tgz", + "integrity": "sha512-bwDp/gKyRtyz0V5Vxy3SATSxItWBK/wDhaacncC79+q1B1VB8SQ49AlVaQCM+XxmIO29cWX4cvsFj65mD2qrzA==", + "requires": { + "@types/pg": "^7.14.4", + "pg": "^8.2.1" + }, + "dependencies": { + "@types/pg": { + "version": "7.14.11", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-7.14.11.tgz", + "integrity": "sha512-EnZkZ1OMw9DvNfQkn2MTJrwKmhJYDEs5ujWrPfvseWNoI95N8B4HzU/Ltrq5ZfYxDX/Zg8mTzwr6UAyTjjFvXA==", + "requires": { + "@types/node": "*", + "pg-protocol": "^1.2.0", + "pg-types": "^2.2.0" + } + }, + "pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "requires": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + } + }, + "postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=" + } + } + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + }, + "cookie-parser": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.5.tgz", + "integrity": "sha512-f13bPUj/gG/5mDr+xLmSxxDsB9DQiTIfhJS/sqjrmfAWiAN+x2O4i/XguTL9yDZ+/IFDanJ+5x7hC4CXT9Tdzw==", + "requires": { + "cookie": "0.4.0", + "cookie-signature": "1.0.6" + } + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "cookiejar": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", + "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==", + "dev": true + }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "dev": true + }, + "cssom": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", + "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", + "dev": true + }, + "cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "requires": { + "cssom": "~0.3.6" + }, + "dependencies": { + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + } + } + }, + "data-urls": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", + "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "dev": true, + "requires": { + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" + } + }, + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "requires": { + "ms": "2.1.2" + } + }, + "decimal.js": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", + "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==", + "dev": true + }, + "decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "requires": { + "mimic-response": "^3.1.0" + }, + "dependencies": { + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" + } + } + }, + "dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", + "dev": true + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true + }, + "defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==" + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "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 + }, + "dicer": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", + "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=", + "requires": { + "readable-stream": "1.1.x", + "streamsearch": "0.1.2" + } + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "diff-sequences": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.0.6.tgz", + "integrity": "sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ==", + "dev": true + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "domexception": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", + "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", + "dev": true, + "requires": { + "webidl-conversions": "^5.0.0" + }, + "dependencies": { + "webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", + "dev": true + } + } + }, + "dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "requires": { + "is-obj": "^2.0.0" + } + }, + "dotenv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==" + }, + "dottie": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.2.tgz", + "integrity": "sha512-fmrwR04lsniq/uSr8yikThDTrM7epXHBAAjH9TbeH3rEA8tdCO7mRzB9hdmdGyJCxF8KERo9CITcm3kGuoyMhg==" + }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "dev": true + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "electron-to-chromium": { + "version": "1.3.855", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.855.tgz", + "integrity": "sha512-4g104x65rHM+Wv/0VPh36s6YRBXP4mebpLSNtwTOpUvxyzAmdQGPoxYdg4MiaxvQnJh2xxcAwDOXYa1SMRr2WQ==", + "dev": true + }, + "emittery": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", + "integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==", + "dev": true + }, + "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 + }, + "enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1" + } + }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-goat": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", + "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", + "dev": true + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "escodegen": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", + "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "dev": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + } + } + }, + "eslint": { + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", + "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "dev": true, + "requires": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.3", + "@humanwhocodes/config-array": "^0.5.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + } + } + }, + "eslint-config-prettier": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-7.2.0.tgz", + "integrity": "sha512-rV4Qu0C3nfJKPOAhFujFxB7RMP+URFyQqqOZW9DMRD7ZDTFyjaIlETU3xzHELt++4ugC0+Jm084HQYkkJe+Ivg==", + "dev": true, + "requires": {} + }, + "eslint-plugin-prettier": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz", + "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + } + }, + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + }, + "espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "requires": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, + "expect": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/expect/-/expect-27.2.4.tgz", + "integrity": "sha512-gOtuonQ8TCnbNNCSw2fhVzRf8EFYDII4nB5NmG4IEV0rbUnW1I5zXvoTntU4iicB/Uh0oZr20NGlOLdJiwsOZA==", + "dev": true, + "requires": { + "@jest/types": "^27.2.4", + "ansi-styles": "^5.0.0", + "jest-get-type": "^27.0.6", + "jest-matcher-utils": "^27.2.4", + "jest-message-util": "^27.2.4", + "jest-regex-util": "^27.0.6" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "express-session": { + "version": "1.17.2", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.2.tgz", + "integrity": "sha512-mPcYcLA0lvh7D4Oqr5aNJFMtBMKPLl++OKKxkHzZ0U0oDq1rpKBnkR5f5vCHR26VeArlTOEF9td4x5IjICksRQ==", + "requires": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.0.2", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.1", + "uid-safe": "~2.1.5" + }, + "dependencies": { + "cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "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==" + } + } + }, + "fast-csv": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/fast-csv/-/fast-csv-4.3.6.tgz", + "integrity": "sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==", + "requires": { + "@fast-csv/format": "4.3.5", + "@fast-csv/parse": "4.3.6" + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "fast-glob": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", + "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true + }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "fb-watchman": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", + "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", + "dev": true, + "requires": { + "bser": "2.1.1" + } + }, + "fecha": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.1.tgz", + "integrity": "sha512-MMMQ0ludy/nBs1/o0zVOiKTpG7qMbonKUzjJgQFEuvq6INZ1OraKPRAWkBq5vlKLOUMpmNYG1JoN3oDPUQ9m3Q==" + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "file-stream-rotator": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.5.7.tgz", + "integrity": "sha512-VYb3HZ/GiAGUCrfeakO8Mp54YGswNUHvL7P09WQcXAJNSj3iQ5QraYSp3cIn1MUyw6uzfgN/EFOarCNa4JvUHQ==", + "requires": { + "moment": "^2.11.2" + } + }, + "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, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "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, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", + "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", + "dev": true + }, + "fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + }, + "form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "formidable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", + "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==", + "dev": true + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "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 + }, + "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 + }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "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 + }, + "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 + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "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, + "requires": { + "is-glob": "^4.0.1" + } + }, + "global-dirs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", + "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", + "dev": true, + "requires": { + "ini": "2.0.0" + } + }, + "globals": { + "version": "13.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.11.0.tgz", + "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "globby": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", + "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + } + }, + "google-libphonenumber": { + "version": "3.2.24", + "resolved": "https://registry.npmjs.org/google-libphonenumber/-/google-libphonenumber-3.2.24.tgz", + "integrity": "sha512-5Z3dZTgwacTjALlkRK5hTIjGLwTCJGNZtmWgnhpb1Z6XEBYGsXbJGsr9+MheP/2mAk/ssOvpvMXKEZISWeytcA==" + }, + "got": { + "version": "11.8.2", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.2.tgz", + "integrity": "sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==", + "requires": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.1", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + } + }, + "graceful-fs": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "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 + }, + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true + }, + "has-yarn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", + "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", + "dev": true + }, + "helmet": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-4.6.0.tgz", + "integrity": "sha512-HVqALKZlR95ROkrnesdhbbZJFi/rIVSoNq6f3jA/9u6MIbTsPh3xZwihjeI5+DO/2sOV6HMHooXcEOuwskHpTg==" + }, + "hpp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/hpp/-/hpp-0.2.3.tgz", + "integrity": "sha512-4zDZypjQcxK/8pfFNR7jaON7zEUpXZxz4viyFmqjb3kWNWAHsLEUmWXcdn25c5l76ISvnD6hbOGO97cXUI3Ryw==", + "requires": { + "lodash": "^4.17.12", + "type-is": "^1.6.12" + } + }, + "html-encoding-sniffer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", + "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "dev": true, + "requires": { + "whatwg-encoding": "^1.0.5" + } + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "dev": true, + "requires": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + } + }, + "http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "requires": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + } + }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "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 + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true + }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", + "dev": true + }, + "import-local": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", + "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==" + }, + "inflection": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.13.1.tgz", + "integrity": "sha512-dldYtl2WlN0QDkIDtg8+xFwOS2Tbmp12t1cHa5/YClU6ZQjTFm7B66UcVbh9NQB+HvT5BAd2t5+yKsBkw5pcqA==" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "dev": true + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-ci": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.0.tgz", + "integrity": "sha512-kDXyttuLeslKAHYL/K28F2YkM3x5jvFPEw3yXbRptXydjD9rpLEz+C5K5iutY9ZiUu6AP41JdvRQwF4Iqs4ZCQ==", + "dev": true, + "requires": { + "ci-info": "^3.1.1" + } + }, + "is-core-module": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.7.0.tgz", + "integrity": "sha512-ByY+tjCciCr+9nLryBYcSD50EOGWt95c7tIsKTG1J2ixKKXPvF7Ej3AVd+UfDydAJom3biBGDBALaO79ktwgEQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-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 + }, + "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, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "dev": true, + "requires": { + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" + } + }, + "is-npm": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz", + "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, + "is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-yarn-global": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", + "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", + "dev": true + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.1.tgz", + "integrity": "sha512-GvCYYTxaCPqwMjobtVcVKvSHtAGe48MNhGjpK8LtVF8K0ISX7hCKl85LgtuaSneWVyQmaGcW3iXVV3GaZSLpmQ==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "requires": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + } + }, + "istanbul-lib-source-maps": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", + "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + } + }, + "istanbul-reports": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", + "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "jest": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest/-/jest-27.2.4.tgz", + "integrity": "sha512-h4uqb1EQLfPulWyUFFWv9e9Nn8sCqsJ/j3wk/KCY0p4s4s0ICCfP3iMf6hRf5hEhsDyvyrCgKiZXma63gMz16A==", + "dev": true, + "requires": { + "@jest/core": "^27.2.4", + "import-local": "^3.0.2", + "jest-cli": "^27.2.4" + } + }, + "jest-changed-files": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.2.4.tgz", + "integrity": "sha512-eeO1C1u4ex7pdTroYXezr+rbr957myyVoKGjcY4R1TJi3A+9v+4fu1Iv9J4eLq1bgFyT3O3iRWU9lZsEE7J72Q==", + "dev": true, + "requires": { + "@jest/types": "^27.2.4", + "execa": "^5.0.0", + "throat": "^6.0.1" + } + }, + "jest-circus": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.2.4.tgz", + "integrity": "sha512-TtheheTElrGjlsY9VxkzUU1qwIx05ItIusMVKnvNkMt4o/PeegLRcjq3Db2Jz0GGdBalJdbzLZBgeulZAJxJWA==", + "dev": true, + "requires": { + "@jest/environment": "^27.2.4", + "@jest/test-result": "^27.2.4", + "@jest/types": "^27.2.4", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "expect": "^27.2.4", + "is-generator-fn": "^2.0.0", + "jest-each": "^27.2.4", + "jest-matcher-utils": "^27.2.4", + "jest-message-util": "^27.2.4", + "jest-runtime": "^27.2.4", + "jest-snapshot": "^27.2.4", + "jest-util": "^27.2.4", + "pretty-format": "^27.2.4", + "slash": "^3.0.0", + "stack-utils": "^2.0.3", + "throat": "^6.0.1" + } + }, + "jest-cli": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.2.4.tgz", + "integrity": "sha512-4kpQQkg74HYLaXo3nzwtg4PYxSLgL7puz1LXHj5Tu85KmlIpxQFjRkXlx4V47CYFFIDoyl3rHA/cXOxUWyMpNg==", + "dev": true, + "requires": { + "@jest/core": "^27.2.4", + "@jest/test-result": "^27.2.4", + "@jest/types": "^27.2.4", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "import-local": "^3.0.2", + "jest-config": "^27.2.4", + "jest-util": "^27.2.4", + "jest-validate": "^27.2.4", + "prompts": "^2.0.1", + "yargs": "^16.2.0" + } + }, + "jest-config": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.2.4.tgz", + "integrity": "sha512-tWy0UxhdzqiKyp4l5Vq4HxLyD+gH5td+GCF3c22/DJ0bYAOsMo+qi2XtbJI6oYMH5JOJQs9nLW/r34nvFCehjA==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/test-sequencer": "^27.2.4", + "@jest/types": "^27.2.4", + "babel-jest": "^27.2.4", + "chalk": "^4.0.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.1", + "graceful-fs": "^4.2.4", + "is-ci": "^3.0.0", + "jest-circus": "^27.2.4", + "jest-environment-jsdom": "^27.2.4", + "jest-environment-node": "^27.2.4", + "jest-get-type": "^27.0.6", + "jest-jasmine2": "^27.2.4", + "jest-regex-util": "^27.0.6", + "jest-resolve": "^27.2.4", + "jest-runner": "^27.2.4", + "jest-util": "^27.2.4", + "jest-validate": "^27.2.4", + "micromatch": "^4.0.4", + "pretty-format": "^27.2.4" + } + }, + "jest-diff": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.2.4.tgz", + "integrity": "sha512-bLAVlDSCR3gqUPGv+4nzVpEXGsHh98HjUL7Vb2hVyyuBDoQmja8eJb0imUABsuxBeUVmf47taJSAd9nDrwWKEg==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^27.0.6", + "jest-get-type": "^27.0.6", + "pretty-format": "^27.2.4" + } + }, + "jest-docblock": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.0.6.tgz", + "integrity": "sha512-Fid6dPcjwepTFraz0YxIMCi7dejjJ/KL9FBjPYhBp4Sv1Y9PdhImlKZqYU555BlN4TQKaTc+F2Av1z+anVyGkA==", + "dev": true, + "requires": { + "detect-newline": "^3.0.0" + } + }, + "jest-each": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.2.4.tgz", + "integrity": "sha512-w9XVc+0EDBUTJS4xBNJ7N2JCcWItFd006lFjz77OarAQcQ10eFDBMrfDv2GBJMKlXe9aq0HrIIF51AXcZrRJyg==", + "dev": true, + "requires": { + "@jest/types": "^27.2.4", + "chalk": "^4.0.0", + "jest-get-type": "^27.0.6", + "jest-util": "^27.2.4", + "pretty-format": "^27.2.4" + } + }, + "jest-environment-jsdom": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.2.4.tgz", + "integrity": "sha512-X70pTXFSypD7AIzKT1mLnDi5hP9w9mdTRcOGOmoDoBrNyNEg4rYm6d4LQWFLc9ps1VnMuDOkFSG0wjSNYGjkng==", + "dev": true, + "requires": { + "@jest/environment": "^27.2.4", + "@jest/fake-timers": "^27.2.4", + "@jest/types": "^27.2.4", + "@types/node": "*", + "jest-mock": "^27.2.4", + "jest-util": "^27.2.4", + "jsdom": "^16.6.0" + } + }, + "jest-environment-node": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.2.4.tgz", + "integrity": "sha512-ZbVbFSnbzTvhLOIkqh5lcLuGCCFvtG4xTXIRPK99rV2KzQT3kNg16KZwfTnLNlIiWCE8do960eToeDfcqmpSAw==", + "dev": true, + "requires": { + "@jest/environment": "^27.2.4", + "@jest/fake-timers": "^27.2.4", + "@jest/types": "^27.2.4", + "@types/node": "*", + "jest-mock": "^27.2.4", + "jest-util": "^27.2.4" + } + }, + "jest-get-type": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.6.tgz", + "integrity": "sha512-XTkK5exIeUbbveehcSR8w0bhH+c0yloW/Wpl+9vZrjzztCPWrxhHwkIFpZzCt71oRBsgxmuUfxEqOYoZI2macg==", + "dev": true + }, + "jest-haste-map": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.2.4.tgz", + "integrity": "sha512-bkJ4bT00T2K+1NZXbRcyKnbJ42I6QBvoDNMTAQQDBhaGNnZreiQKUNqax0e6hLTx7E75pKDeltVu3V1HAdu+YA==", + "dev": true, + "requires": { + "@jest/types": "^27.2.4", + "@types/graceful-fs": "^4.1.2", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.4", + "jest-regex-util": "^27.0.6", + "jest-serializer": "^27.0.6", + "jest-util": "^27.2.4", + "jest-worker": "^27.2.4", + "micromatch": "^4.0.4", + "walker": "^1.0.7" + } + }, + "jest-jasmine2": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.2.4.tgz", + "integrity": "sha512-fcffjO/xLWLVnW2ct3No4EksxM5RyPwHDYu9QU+90cC+/eSMLkFAxS55vkqsxexOO5zSsZ3foVpMQcg/amSeIQ==", + "dev": true, + "requires": { + "@babel/traverse": "^7.1.0", + "@jest/environment": "^27.2.4", + "@jest/source-map": "^27.0.6", + "@jest/test-result": "^27.2.4", + "@jest/types": "^27.2.4", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "expect": "^27.2.4", + "is-generator-fn": "^2.0.0", + "jest-each": "^27.2.4", + "jest-matcher-utils": "^27.2.4", + "jest-message-util": "^27.2.4", + "jest-runtime": "^27.2.4", + "jest-snapshot": "^27.2.4", + "jest-util": "^27.2.4", + "pretty-format": "^27.2.4", + "throat": "^6.0.1" + } + }, + "jest-leak-detector": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.2.4.tgz", + "integrity": "sha512-SrcHWbe0EHg/bw2uBjVoHacTo5xosl068x2Q0aWsjr2yYuW2XwqrSkZV4lurUop0jhv1709ymG4or+8E4sH27Q==", + "dev": true, + "requires": { + "jest-get-type": "^27.0.6", + "pretty-format": "^27.2.4" + } + }, + "jest-matcher-utils": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.2.4.tgz", + "integrity": "sha512-nQeLfFAIPPkyhkDfifAPfP/U5wm1x0fLtAzqXZSSKckXDNuk2aaOfQiDYv1Mgf5GY6yOsxfUnvNm3dDjXM+BXw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^27.2.4", + "jest-get-type": "^27.0.6", + "pretty-format": "^27.2.4" + } + }, + "jest-message-util": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.2.4.tgz", + "integrity": "sha512-wbKT/BNGnBVB9nzi+IoaLkXt6fbSvqUxx+IYY66YFh96J3goY33BAaNG3uPqaw/Sh/FR9YpXGVDfd5DJdbh4nA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^27.2.4", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.4", + "pretty-format": "^27.2.4", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.14.5" + } + } + } + }, + "jest-mock": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.2.4.tgz", + "integrity": "sha512-iVRU905rutaAoUcrt5Tm1JoHHWi24YabqEGXjPJI4tAyA6wZ7mzDi3GrZ+M7ebgWBqUkZE93GAx1STk7yCMIQA==", + "dev": true, + "requires": { + "@jest/types": "^27.2.4", + "@types/node": "*" + } + }, + "jest-pnp-resolver": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", + "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", + "dev": true, + "requires": {} + }, + "jest-regex-util": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.0.6.tgz", + "integrity": "sha512-SUhPzBsGa1IKm8hx2F4NfTGGp+r7BXJ4CulsZ1k2kI+mGLG+lxGrs76veN2LF/aUdGosJBzKgXmNCw+BzFqBDQ==", + "dev": true + }, + "jest-resolve": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.2.4.tgz", + "integrity": "sha512-IsAO/3+3BZnKjI2I4f3835TBK/90dxR7Otgufn3mnrDFTByOSXclDi3G2XJsawGV4/18IMLARJ+V7Wm7t+J89Q==", + "dev": true, + "requires": { + "@jest/types": "^27.2.4", + "chalk": "^4.0.0", + "escalade": "^3.1.1", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^27.2.4", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^27.2.4", + "jest-validate": "^27.2.4", + "resolve": "^1.20.0", + "slash": "^3.0.0" + } + }, + "jest-resolve-dependencies": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.2.4.tgz", + "integrity": "sha512-i5s7Uh9B3Q6uwxLpMhNKlgBf6pcemvWaORxsW1zNF/YCY3jd5EftvnGBI+fxVwJ1CBxkVfxqCvm1lpZkbaoGmg==", + "dev": true, + "requires": { + "@jest/types": "^27.2.4", + "jest-regex-util": "^27.0.6", + "jest-snapshot": "^27.2.4" + } + }, + "jest-runner": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.2.4.tgz", + "integrity": "sha512-hIo5PPuNUyVDidZS8EetntuuJbQ+4IHWxmHgYZz9FIDbG2wcZjrP6b52uMDjAEQiHAn8yn8ynNe+TL8UuGFYKg==", + "dev": true, + "requires": { + "@jest/console": "^27.2.4", + "@jest/environment": "^27.2.4", + "@jest/test-result": "^27.2.4", + "@jest/transform": "^27.2.4", + "@jest/types": "^27.2.4", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.8.1", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "jest-docblock": "^27.0.6", + "jest-environment-jsdom": "^27.2.4", + "jest-environment-node": "^27.2.4", + "jest-haste-map": "^27.2.4", + "jest-leak-detector": "^27.2.4", + "jest-message-util": "^27.2.4", + "jest-resolve": "^27.2.4", + "jest-runtime": "^27.2.4", + "jest-util": "^27.2.4", + "jest-worker": "^27.2.4", + "source-map-support": "^0.5.6", + "throat": "^6.0.1" + } + }, + "jest-runtime": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.2.4.tgz", + "integrity": "sha512-ICKzzYdjIi70P17MZsLLIgIQFCQmIjMFf+xYww3aUySiUA/QBPUTdUqo5B2eg4HOn9/KkUsV0z6GVgaqAPBJvg==", + "dev": true, + "requires": { + "@jest/console": "^27.2.4", + "@jest/environment": "^27.2.4", + "@jest/fake-timers": "^27.2.4", + "@jest/globals": "^27.2.4", + "@jest/source-map": "^27.0.6", + "@jest/test-result": "^27.2.4", + "@jest/transform": "^27.2.4", + "@jest/types": "^27.2.4", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "execa": "^5.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^27.2.4", + "jest-message-util": "^27.2.4", + "jest-mock": "^27.2.4", + "jest-regex-util": "^27.0.6", + "jest-resolve": "^27.2.4", + "jest-snapshot": "^27.2.4", + "jest-util": "^27.2.4", + "jest-validate": "^27.2.4", + "slash": "^3.0.0", + "strip-bom": "^4.0.0", + "yargs": "^16.2.0" + } + }, + "jest-serializer": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.0.6.tgz", + "integrity": "sha512-PtGdVK9EGC7dsaziskfqaAPib6wTViY3G8E5wz9tLVPhHyiDNTZn/xjZ4khAw+09QkoOVpn7vF5nPSN6dtBexA==", + "dev": true, + "requires": { + "@types/node": "*", + "graceful-fs": "^4.2.4" + } + }, + "jest-snapshot": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.2.4.tgz", + "integrity": "sha512-5DFxK31rYS8X8C6WXsFx8XxrxW3PGa6+9IrUcZdTLg1aEyXDGIeiBh4jbwvh655bg/9vTETbEj/njfZicHTZZw==", + "dev": true, + "requires": { + "@babel/core": "^7.7.2", + "@babel/generator": "^7.7.2", + "@babel/parser": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.0.0", + "@jest/transform": "^27.2.4", + "@jest/types": "^27.2.4", + "@types/babel__traverse": "^7.0.4", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^27.2.4", + "graceful-fs": "^4.2.4", + "jest-diff": "^27.2.4", + "jest-get-type": "^27.0.6", + "jest-haste-map": "^27.2.4", + "jest-matcher-utils": "^27.2.4", + "jest-message-util": "^27.2.4", + "jest-resolve": "^27.2.4", + "jest-util": "^27.2.4", + "natural-compare": "^1.4.0", + "pretty-format": "^27.2.4", + "semver": "^7.3.2" + } + }, + "jest-util": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.2.4.tgz", + "integrity": "sha512-mW++4u+fSvAt3YBWm5IpbmRAceUqa2B++JlUZTiuEt2AmNYn0Yw5oay4cP17TGsMINRNPSGiJ2zNnX60g+VbFg==", + "dev": true, + "requires": { + "@jest/types": "^27.2.4", + "@types/node": "*", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^3.0.0", + "picomatch": "^2.2.3" + } + }, + "jest-validate": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.2.4.tgz", + "integrity": "sha512-VMtbxbkd7LHnIH7PChdDtrluCFRJ4b1YV2YJzNwwsASMWftq/HgqiqjvptBOWyWOtevgO3f14wPxkPcLlVBRog==", + "dev": true, + "requires": { + "@jest/types": "^27.2.4", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^27.0.6", + "leven": "^3.1.0", + "pretty-format": "^27.2.4" + }, + "dependencies": { + "camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true + } + } + }, + "jest-watcher": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.2.4.tgz", + "integrity": "sha512-LXC/0+dKxhK7cfF7reflRYlzDIaQE+fL4ynhKhzg8IMILNMuI4xcjXXfUJady7OR4/TZeMg7X8eHx8uan9vqaQ==", + "dev": true, + "requires": { + "@jest/test-result": "^27.2.4", + "@jest/types": "^27.2.4", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "jest-util": "^27.2.4", + "string-length": "^4.0.1" + } + }, + "jest-worker": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.2.4.tgz", + "integrity": "sha512-Zq9A2Pw59KkVjBBKD1i3iE2e22oSjXhUKKuAK1HGX8flGwkm6NMozyEYzKd41hXc64dbd/0eWFeEEuxqXyhM+g==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "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, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jose": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.5.tgz", + "integrity": "sha512-BAiDNeDKTMgk4tvD0BbxJ8xHEHBZgpeRZ1zGPPsitSyMgjoMWiLGYAE7H7NpP5h0lPppQajQs871E8NHUrzVPA==", + "requires": { + "@panva/asn1.js": "^1.0.0" + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "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, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsdom": { + "version": "16.7.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", + "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", + "dev": true, + "requires": { + "abab": "^2.0.5", + "acorn": "^8.2.4", + "acorn-globals": "^6.0.0", + "cssom": "^0.4.4", + "cssstyle": "^2.3.0", + "data-urls": "^2.0.0", + "decimal.js": "^10.2.1", + "domexception": "^2.0.1", + "escodegen": "^2.0.0", + "form-data": "^3.0.0", + "html-encoding-sniffer": "^2.0.1", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.0", + "parse5": "6.0.1", + "saxes": "^5.0.1", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.0.0", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^2.0.0", + "webidl-conversions": "^6.1.0", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.5.0", + "ws": "^7.4.6", + "xml-name-validator": "^3.0.0" + }, + "dependencies": { + "acorn": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", + "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==", + "dev": true + } + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "json5": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", + "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "keyv": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz", + "integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==", + "requires": { + "json-buffer": "3.0.1" + } + }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true + }, + "kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + }, + "latest-version": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", + "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", + "dev": true, + "requires": { + "package-json": "^6.3.0" + } + }, + "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 + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "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, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=" + }, + "lodash.groupby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", + "integrity": "sha1-Cwih3PaDl8OXhVwyOXg4Mt90A9E=" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" + }, + "lodash.isfunction": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", + "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==" + }, + "lodash.isnil": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lodash.isnil/-/lodash.isnil-4.0.0.tgz", + "integrity": "sha1-SeKM1VkBNFjIFMVHnTxmOiG/qmw=" + }, + "lodash.isundefined": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz", + "integrity": "sha1-I+89lTVWUgOmbO/VuDD4SJEa+0g=" + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", + "dev": true + }, + "lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" + }, + "logform": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.3.0.tgz", + "integrity": "sha512-graeoWUH2knKbGthMtuG1EfaSPMZFZBIrhuJHhkS5ZseFBrc7DupCzihOQAzsK/qIKPQaPJ/lFQFctILUY5ARQ==", + "requires": { + "colors": "^1.2.1", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^1.1.0", + "triple-beam": "^1.3.0" + } + }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" + }, + "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==", + "requires": { + "yallist": "^4.0.0" + } + }, + "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, + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" + }, + "makeerror": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", + "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", + "dev": true, + "requires": { + "tmpl": "1.0.x" + } + }, + "marked": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/marked/-/marked-3.0.4.tgz", + "integrity": "sha512-jBo8AOayNaEcvBhNobg6/BLhdsK3NvnKWJg33MAAPbvTWiG4QBn9gpW1+7RssrKu4K1dKlN+0goVQwV41xEfOA==" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "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 + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.50.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.50.0.tgz", + "integrity": "sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==" + }, + "mime-types": { + "version": "2.1.32", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", + "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", + "requires": { + "mime-db": "1.49.0" + }, + "dependencies": { + "mime-db": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", + "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==" + } + } + }, + "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 + }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + }, + "moment": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" + }, + "moment-timezone": { + "version": "0.5.33", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.33.tgz", + "integrity": "sha512-PTc2vcT8K9J5/9rDEPe5czSIKgLoGsH8UNpA4qZTVw0Vd/Uz19geE9abbIOQKaAQFcnQ3v5YEXrbSc5BpshH+w==", + "requires": { + "moment": ">= 2.9.0" + } + }, + "morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "requires": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "multer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.3.tgz", + "integrity": "sha512-np0YLKncuZoTzufbkM6wEKp68EhWJXcU6fq6QqrSwkckd2LlMgd1UqhUJLj6NS/5sZ8dE8LYDWslsltJznnXlg==", + "requires": { + "append-field": "^1.0.0", + "busboy": "^0.2.11", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "on-finished": "^2.3.0", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + } + }, + "nanocolors": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/nanocolors/-/nanocolors-0.2.12.tgz", + "integrity": "sha512-SFNdALvzW+rVlzqexid6epYdt8H9Zol7xDoQarioEFcFN0JHo4CYNztAxmtfgGTVRCmFlEOqqhBpoFGKqSAMug==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, + "node-forge": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", + "dev": true + }, + "node-jose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/node-jose/-/node-jose-2.0.0.tgz", + "integrity": "sha512-j8zoFze1gijl8+DK/dSXXqX7+o2lMYv1XS+ptnXgGV/eloQaqq1YjNtieepbKs9jBS4WTnMOqyKSaQuunJzx0A==", + "requires": { + "base64url": "^3.0.1", + "buffer": "^5.5.0", + "es6-promise": "^4.2.8", + "lodash": "^4.17.15", + "long": "^4.0.0", + "node-forge": "^0.10.0", + "pako": "^1.0.11", + "process": "^0.11.10", + "uuid": "^3.3.3" + }, + "dependencies": { + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + } + } + }, + "node-modules-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", + "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", + "dev": true + }, + "node-releases": { + "version": "1.1.76", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.76.tgz", + "integrity": "sha512-9/IECtNr8dXNmPWmFXepT0/7o5eolGesHUa3mtr0KlgnCvnZxwh2qensKL42JJY2vQKC3nIBXetFAqR+PW1CmA==", + "dev": true + }, + "nodemon": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.13.tgz", + "integrity": "sha512-UMXMpsZsv1UXUttCn6gv8eQPhn6DR4BW+txnL3IN5IHqrCwcrT/yWHfL35UsClGXknTH79r5xbu+6J1zNHuSyA==", + "dev": true, + "requires": { + "chokidar": "^3.2.2", + "debug": "^3.2.6", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.0.4", + "pstree.remy": "^1.1.7", + "semver": "^5.7.1", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.3", + "update-notifier": "^5.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==" + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "nwsapi": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", + "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==" + }, + "object-inspect": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", + "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", + "dev": true + }, + "obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "oidc-token-hash": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.1.tgz", + "integrity": "sha512-EvoOtz6FIEBzE+9q253HsLCVRiK/0doEJ2HCvvqMQb3dHZrP3WlJKYtJ55CRTw4jmYomzH4wkPuCj/I3ZvpKxQ==" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "requires": { + "fn.name": "1.x.x" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "openid-client": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-4.9.0.tgz", + "integrity": "sha512-ThBbvRUUZwxUKBVK2UpDNIZ3eJkvtqWI8s5Dm+naV+gJdL+yRhT+8ywqct1gy5uL+xVS5+A/nhFcpJIisH2x6Q==", + "requires": { + "aggregate-error": "^3.1.0", + "got": "^11.8.0", + "jose": "^2.0.5", + "lru-cache": "^6.0.0", + "make-error": "^1.3.6", + "object-hash": "^2.0.1", + "oidc-token-hash": "^5.0.1" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==" + }, + "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, + "requires": { + "p-try": "^2.0.0" + } + }, + "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, + "requires": { + "p-limit": "^2.2.0" + } + }, + "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 + }, + "package-json": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", + "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", + "dev": true, + "requires": { + "got": "^9.6.0", + "registry-auth-token": "^4.0.0", + "registry-url": "^5.0.0", + "semver": "^6.2.0" + }, + "dependencies": { + "@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "dev": true + }, + "@szmarczak/http-timer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", + "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "dev": true, + "requires": { + "defer-to-connect": "^1.0.1" + } + }, + "cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "dev": true, + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^3.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^1.0.2" + }, + "dependencies": { + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + } + } + }, + "decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "dev": true, + "requires": { + "mimic-response": "^1.0.0" + } + }, + "defer-to-connect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", + "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "dev": true, + "requires": { + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" + }, + "dependencies": { + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true + } + } + }, + "json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", + "dev": true + }, + "keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "dev": true, + "requires": { + "json-buffer": "3.0.0" + } + }, + "normalize-url": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", + "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", + "dev": true + }, + "p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", + "dev": true + }, + "responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "dev": true, + "requires": { + "lowercase-keys": "^1.0.0" + }, + "dependencies": { + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true + } + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "passport": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.4.1.tgz", + "integrity": "sha512-IxXgZZs8d7uFSt3eqNjM9NQ3g3uQCW5avD8mRNoXV99Yig50vjuaez6dQK2qC0kVWPRTujxY0dWgGfT09adjYg==", + "requires": { + "passport-strategy": "1.x.x", + "pause": "0.0.1" + } + }, + "passport-custom": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/passport-custom/-/passport-custom-1.1.1.tgz", + "integrity": "sha512-/2m7jUGxmCYvoqenLB9UrmkCgPt64h8ZtV+UtuQklZ/Tn1NpKBeOorCYkB/8lMRoiZ5hUrCoMmDtxCS/d38mlg==", + "requires": { + "passport-strategy": "1.x.x" + } + }, + "passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=" + }, + "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 + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "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 + }, + "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 + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" + }, + "pg": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.7.1.tgz", + "integrity": "sha512-7bdYcv7V6U3KAtWjpQJJBww0UEsWuh4yQ/EjNf2HeO/NnvKjpvhEIe/A/TleP6wtmSKnUnghs5A9jUoK6iDdkA==", + "requires": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "^2.5.0", + "pg-pool": "^3.4.1", + "pg-protocol": "^1.5.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + }, + "dependencies": { + "pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "requires": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + } + }, + "postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=" + } + } + }, + "pg-connection-string": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", + "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" + }, + "pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" + }, + "pg-numeric": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pg-numeric/-/pg-numeric-1.0.2.tgz", + "integrity": "sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==", + "dev": true + }, + "pg-pool": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.4.1.tgz", + "integrity": "sha512-TVHxR/gf3MeJRvchgNHxsYsTCHQ+4wm3VIHSS19z8NC0+gioEhq1okDY1sm/TYbfoP6JLFx01s0ShvZ3puP/iQ==", + "requires": {} + }, + "pg-protocol": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz", + "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==" + }, + "pg-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-3.0.1.tgz", + "integrity": "sha512-Q3zN2GyDEOrc9m6lxCru9JakbdVQY5/ylKxSXI/PcIQPVRDNBR7BxdeCtxa9WI+UIoE3JNcyAffGThOxd+V/4g==", + "dev": true, + "requires": { + "pg-int8": "1.0.1", + "pg-numeric": "1.0.2", + "postgres-array": "~2.0.0", + "postgres-bytea": "~3.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + } + }, + "pgpass": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.4.tgz", + "integrity": "sha512-YmuA56alyBq7M59vxVBfPJrGSozru8QAdoNlWuW3cz8l+UX3cWge0vTvjKhsSHSJpo3Bom8/Mm6hf0TR5GY0+w==", + "requires": { + "split2": "^3.1.1" + } + }, + "picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true + }, + "pirates": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", + "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", + "dev": true, + "requires": { + "node-modules-regexp": "^1.0.0" + } + }, + "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, + "requires": { + "find-up": "^4.0.0" + } + }, + "postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" + }, + "postgres-bytea": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-3.0.0.tgz", + "integrity": "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==", + "dev": true, + "requires": { + "obuf": "~1.1.2" + } + }, + "postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==" + }, + "postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "requires": { + "xtend": "^4.0.0" + } + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", + "dev": true + }, + "prettier": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.1.tgz", + "integrity": "sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==", + "dev": true + }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, + "pretty-format": { + "version": "27.2.4", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.2.4.tgz", + "integrity": "sha512-NUjw22WJHldzxyps2YjLZkUj6q1HvjqFezkB9Y2cklN8NtVZN/kZEXGZdFw4uny3oENzV5EEMESrkI0YDUH8vg==", + "dev": true, + "requires": { + "@jest/types": "^27.2.4", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "prompts": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.1.tgz", + "integrity": "sha512-EQyfIuO2hPDsX1L/blblV+H7I0knhgAd82cVneCwcdND9B8AuCDuRcBH6yIcG4dFzlOUqbazQqwGjx5xmsNLuQ==", + "dev": true, + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + } + }, + "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==", + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "dev": true + }, + "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 + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "pupa": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", + "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", + "dev": true, + "requires": { + "escape-goat": "^2.0.0" + } + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" + }, + "random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "dependencies": { + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + } + } + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + } + } + }, + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, + "registry-auth-token": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", + "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", + "dev": true, + "requires": { + "rc": "^1.2.8" + } + }, + "registry-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", + "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", + "dev": true, + "requires": { + "rc": "^1.2.8" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + }, + "resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" + }, + "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, + "requires": { + "resolve-from": "^5.0.0" + }, + "dependencies": { + "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 + } + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "responselike": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", + "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", + "requires": { + "lowercase-keys": "^2.0.0" + } + }, + "retry-as-promised": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-3.2.0.tgz", + "integrity": "sha512-CybGs60B7oYU/qSQ6kuaFmRd9sTZ6oXSc0toqePvV74Ac6/IFZSI1ReFQmtCN+uvW1Mtqdwpvt/LGOiCBAY2Mg==", + "requires": { + "any-promise": "^1.3.0" + } + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-stable-stringify": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-1.1.1.tgz", + "integrity": "sha512-ERq4hUjKDbJfE4+XtZLFPCDi8Vb1JqaxAPTxWFLBx8XcAlf9Bda/ZJdVezs/NAfsMQScyIlUMx+Yeu7P7rx5jw==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "dev": true, + "requires": { + "xmlchars": "^2.2.0" + } + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "semver-diff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", + "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", + "dev": true, + "requires": { + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "sequelize": { + "version": "6.6.5", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.6.5.tgz", + "integrity": "sha512-QyRrJrDRiwuiILqTMHUA1yWOPIL12KlfmgZ3hnzQwbMvp2vJ6fzu9bYJQB+qPMosck4mBUggY4Cjoc6Et8FBIQ==", + "requires": { + "debug": "^4.1.1", + "dottie": "^2.0.0", + "inflection": "1.13.1", + "lodash": "^4.17.20", + "moment": "^2.26.0", + "moment-timezone": "^0.5.31", + "retry-as-promised": "^3.2.0", + "semver": "^7.3.2", + "sequelize-pool": "^6.0.0", + "toposort-class": "^1.0.1", + "uuid": "^8.1.0", + "validator": "^13.6.0", + "wkx": "^0.5.0" + }, + "dependencies": { + "validator": { + "version": "13.6.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.6.0.tgz", + "integrity": "sha512-gVgKbdbHgtxpRyR8K0O6oFZPhhB5tT1jeEHZR0Znr9Svg03U0+r9DXWMrnRAB+HtCStDQKlaIZm42tVsVjqtjg==" + } + } + }, + "sequelize-mock": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/sequelize-mock/-/sequelize-mock-0.10.2.tgz", + "integrity": "sha1-GdOXHM2utbhkFwwkznkqinHxRL0=", + "dev": true, + "requires": { + "bluebird": "^3.4.6", + "inflection": "^1.10.0", + "lodash": "^4.16.4" + } + }, + "sequelize-pool": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-6.1.0.tgz", + "integrity": "sha512-4YwEw3ZgK/tY/so+GfnSgXkdwIJJ1I32uZJztIEgZeAO6HMgj64OzySbWLgxj+tXhZCJnzRfkY9gINw8Ft8ZMg==" + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "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, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "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 + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "signal-exit": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.5.tgz", + "integrity": "sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==", + "dev": true + }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "requires": { + "is-arrayish": "^0.3.1" + } + }, + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.20", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", + "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "split2": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "requires": { + "readable-stream": "^3.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "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==" + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + } + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" + }, + "stack-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", + "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + } + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "streamsearch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "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, + "requires": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "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, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "superagent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-6.1.0.tgz", + "integrity": "sha512-OUDHEssirmplo3F+1HWKUrUjvnQuA+nZI6i/JJBdXb5eq9IyEQwPyPpqND+SSsxf6TygpBEkUjISVRN4/VOpeg==", + "dev": true, + "requires": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.2", + "debug": "^4.1.1", + "fast-safe-stringify": "^2.0.7", + "form-data": "^3.0.0", + "formidable": "^1.2.2", + "methods": "^1.1.2", + "mime": "^2.4.6", + "qs": "^6.9.4", + "readable-stream": "^3.6.0", + "semver": "^7.3.2" + }, + "dependencies": { + "mime": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", + "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", + "dev": true + }, + "qs": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", + "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", + "dev": true, + "requires": { + "side-channel": "^1.0.4" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + } + } + }, + "supertest": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.1.6.tgz", + "integrity": "sha512-0hACYGNJ8OHRg8CRITeZOdbjur7NLuNs0mBjVhdpxi7hP6t3QIbOzLON5RTUmZcy2I9riuII3+Pr2C7yztrIIg==", + "dev": true, + "requires": { + "methods": "^1.1.2", + "superagent": "^6.1.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-hyperlinks": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", + "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", + "dev": true, + "requires": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + } + }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "table": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/table/-/table-6.7.2.tgz", + "integrity": "sha512-UFZK67uvyNivLeQbVtkiUs8Uuuxv24aSL4/Vil2PJVtMgU8Lx0CYkP12uCGa3kjyQzOSgV1+z9Wkb82fCGsO0g==", + "dev": true, + "requires": { + "ajv": "^8.0.1", + "lodash.clonedeep": "^4.5.0", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "ajv": { + "version": "8.6.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", + "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + } + } + }, + "terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + } + }, + "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, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, + "text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "throat": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", + "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==", + "dev": true + }, + "tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "to-readable-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, + "toposort-class": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", + "integrity": "sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg=" + }, + "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, + "requires": { + "nopt": "~1.0.10" + } + }, + "tough-cookie": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", + "dev": true, + "requires": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.1.2" + } + }, + "tr46": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", + "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", + "dev": true, + "requires": { + "punycode": "^2.1.1" + } + }, + "triple-beam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", + "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" + }, + "ts-jest": { + "version": "27.0.5", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-27.0.5.tgz", + "integrity": "sha512-lIJApzfTaSSbtlksfFNHkWOzLJuuSm4faFAfo5kvzOiRAuoN4/eKxVJ2zEAho8aecE04qX6K1pAzfH5QHL1/8w==", + "dev": true, + "requires": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^27.0.0", + "json5": "2.x", + "lodash": "4.x", + "make-error": "1.x", + "semver": "7.x", + "yargs-parser": "20.x" + } + }, + "ts-node": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.2.1.tgz", + "integrity": "sha512-hCnyOyuGmD5wHleOQX6NIjJtYVIO8bPP8F2acWkB4W06wdlkgyvJtubO/I9NkI88hCFECbsEgoLc0VNkYmcSfw==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "0.6.1", + "@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", + "yn": "3.1.1" + }, + "dependencies": { + "acorn": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", + "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==", + "dev": true + }, + "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==", + "dev": true + } + } + }, + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-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 + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "requires": { + "is-typedarray": "^1.0.0" + } + }, + "typescript": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.3.tgz", + "integrity": "sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==", + "dev": true + }, + "uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "requires": { + "random-bytes": "~1.0.0" + } + }, + "undefsafe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", + "integrity": "sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==", + "dev": true, + "requires": { + "debug": "^2.2.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dev": true, + "requires": { + "crypto-random-string": "^2.0.0" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "update-notifier": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz", + "integrity": "sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==", + "dev": true, + "requires": { + "boxen": "^5.0.0", + "chalk": "^4.1.0", + "configstore": "^5.0.1", + "has-yarn": "^2.1.0", + "import-lazy": "^2.1.0", + "is-ci": "^2.0.0", + "is-installed-globally": "^0.4.0", + "is-npm": "^5.0.0", + "is-yarn-global": "^0.3.0", + "latest-version": "^5.1.0", + "pupa": "^2.1.1", + "semver": "^7.3.4", + "semver-diff": "^3.1.1", + "xdg-basedir": "^4.0.0" + }, + "dependencies": { + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + } + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", + "dev": true, + "requires": { + "prepend-http": "^2.0.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + }, + "v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "v8-to-istanbul": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.0.tgz", + "integrity": "sha512-/PRhfd8aTNp9Ggr62HPzXg2XasNFGy5PBt0Rp04du7/8GNNSgxFL6WBTkgMKSL9bFjH+8kKEG3f37FmxiTqUUA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0", + "source-map": "^0.7.3" + }, + "dependencies": { + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + } + } + }, + "validator": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.0.0.tgz", + "integrity": "sha512-anYx5fURbgF04lQV18nEQWZ/3wHGnxiKdG4aL8J+jEDsm98n/sU/bey+tYk6tnGJzm7ioh5FoqrAiQ6m03IgaA==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "dev": true, + "requires": { + "browser-process-hrtime": "^1.0.0" + } + }, + "w3c-xmlserializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", + "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "dev": true, + "requires": { + "xml-name-validator": "^3.0.0" + } + }, + "walker": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", + "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", + "dev": true, + "requires": { + "makeerror": "1.0.x" + } + }, + "webidl-conversions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "dev": true + }, + "whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dev": true, + "requires": { + "iconv-lite": "0.4.24" + } + }, + "whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "dev": true + }, + "whatwg-url": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", + "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", + "dev": true, + "requires": { + "lodash": "^4.7.0", + "tr46": "^2.1.0", + "webidl-conversions": "^6.1.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "dev": true, + "requires": { + "string-width": "^4.0.0" + } + }, + "winston": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.3.3.tgz", + "integrity": "sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw==", + "requires": { + "@dabh/diagnostics": "^2.0.2", + "async": "^3.1.0", + "is-stream": "^2.0.0", + "logform": "^2.2.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.4.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "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==" + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + } + } + }, + "winston-daily-rotate-file": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-4.5.5.tgz", + "integrity": "sha512-ds0WahIjiDhKCiMXmY799pDBW+58ByqIBtUcsqr4oDoXrAI3Zn+hbgFdUxzMfqA93OG0mPLYVMiotqTgE/WeWQ==", + "requires": { + "file-stream-rotator": "^0.5.7", + "object-hash": "^2.0.1", + "triple-beam": "^1.3.0", + "winston-transport": "^4.4.0" + } + }, + "winston-transport": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.4.0.tgz", + "integrity": "sha512-Lc7/p3GtqtqPBYYtS6KCN3c77/2QCev51DvcJKbkFPQNoj1sinkGwLGFDxkXY9J6p9+EPnYs+D90uwbnaiURTw==", + "requires": { + "readable-stream": "^2.3.7", + "triple-beam": "^1.2.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "wkx": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz", + "integrity": "sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==", + "requires": { + "@types/node": "*" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "ws": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz", + "integrity": "sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==", + "dev": true, + "requires": {} + }, + "xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "dev": true + }, + "xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true + }, + "xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + } + } +} diff --git a/api/package.json b/api/package.json new file mode 100644 index 0000000..4200bfb --- /dev/null +++ b/api/package.json @@ -0,0 +1,91 @@ +{ + "name": "smeqa-art", + "version": "4.1.0", + "description": "", + "main": "build/server.js", + "scripts": { + "test": "npm run test:unit && npm run test:integration", + "test:unit": "jest ./src/tests/services.tests", + "test:integration": "NODE_ENV=development jest --runInBand --detectOpenHandles ./src/tests/integration.tests", + "test:queue": "NODE_ENV=development jest --runInBand --detectOpenHandles ./src/tests/integration.tests/assignmentsQueue.int.spec.ts", + "build": "rm -rf build/ && tsc -p . && npm run build:assets", + "build:assets": "cp package*.json build/ && cp -pR src/client build/client", + "build:vendorDependencies": "npm run build && cp -pR node_modules build", + "dev": "nodemon --watch 'src/**/*.ts' --exec node --inspect=0.0.0.0:9229 -r ts-node/register src/server.ts", + "start": "node server.js", + "start:ts": "npm run build && node --inspect=0.0.0.0:9222 --nolazy build/server.js", + "start:dev": "nodemon --watch 'src/**/*.ts' --exec \"npm run start:ts\"", + "tunnel": "myapp_guid=$(cf app --guid $APP_NAME) && TUNNEL=$(cf curl /v2/apps/$myapp_guid/env | jq -r '[.system_env_json.VCAP_SERVICES.\"aws-rds\"[0].credentials | .host, .port] | join(\":\")') && echo 'Rat tunnel active' &&cf ssh -N -L 5432:$TUNNEL $APP_NAME", + "tunnel:stage": "APP_NAME=smeqa-staging npm run tunnel", + "tunnel:demo": "APP_NAME=smeqa-demo npm run tunnel", + "tunnel:prod": "APP_NAME=smeqa-rr npm run tunnel", + "watch": "tsc -w -p ." + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@types/compression": "^1.7.0", + "@types/connect-pg-simple": "^4.2.2", + "@types/cookie-parser": "^1.4.2", + "@types/cors": "^2.8.9", + "@types/express": "^4.17.9", + "@types/express-session": "^1.17.3", + "@types/hpp": "^0.2.1", + "@types/marked": "^3.0.1", + "@types/morgan": "^1.9.2", + "@types/multer": "^1.4.5", + "@types/node": "^14.14.14", + "@types/node-jose": "^1.1.5", + "@types/passport": "^1.0.4", + "@types/pg": "7.4.8", + "@types/sequelize": "^4.28.9", + "@types/showdown": "^1.9.4", + "@types/supertest": "^2.0.10", + "@types/uuid": "^8.3.0", + "@types/validator": "^13.1.1", + "@typescript-eslint/eslint-plugin": "^4.10.0", + "@typescript-eslint/parser": "^4.10.0", + "eslint": "^7.15.0", + "eslint-config-prettier": "^7.0.0", + "eslint-plugin-prettier": "^3.3.0", + "jest": "^27.0.6", + "nodemon": "^2.0.6", + "prettier": "^2.2.1", + "sequelize-mock": "^0.10.2", + "supertest": "^6.0.1", + "ts-jest": "^27.0.3", + "ts-node": "^10.1.0", + "typescript": "^4.1.3", + "@types/jest": "^27.0.2" + }, + "dependencies": { + "class-transformer": "^0.3.1", + "class-validator": "^0.12.2", + "compression": "^1.7.4", + "connect-pg-simple": "^6.2.1", + "cookie-parser": "^1.4.5", + "cors": "^2.8.5", + "dotenv": "^8.2.0", + "express": "^4.17.1", + "express-session": "^1.17.1", + "fast-csv": "^4.3.6", + "helmet": "^4.2.0", + "hpp": "^0.2.3", + "marked": "^3.0.4", + "morgan": "^1.10.0", + "multer": "^1.4.2", + "node-jose": "^2.0.0", + "openid-client": "^4.7.4", + "passport": "^0.4.1", + "passport-custom": "^1.1.1", + "pg": "^8.5.1", + "sequelize": "^6.5.0", + "uuid": "^8.3.2", + "winston": "^3.3.3", + "winston-daily-rotate-file": "^4.5.0" + }, + "engines": { + "node": "14.18.1" + } +} diff --git a/api/src/@types/array/index.d.ts b/api/src/@types/array/index.d.ts new file mode 100644 index 0000000..583b589 --- /dev/null +++ b/api/src/@types/array/index.d.ts @@ -0,0 +1,6 @@ +declare global { + interface Array { + flat(): Array; + flatMap(func: (x: T) => T): Array; + } +} diff --git a/api/src/@types/express/index.d.ts b/api/src/@types/express/index.d.ts new file mode 100644 index 0000000..c237b96 --- /dev/null +++ b/api/src/@types/express/index.d.ts @@ -0,0 +1,10 @@ +import {} from 'express'; //do not delete this - required for augmenting module declarations +declare global { + namespace Express { + export interface User { + id: string; + email: string; + name: string; + } + } +} diff --git a/api/src/app.ts b/api/src/app.ts new file mode 100644 index 0000000..cd807ed --- /dev/null +++ b/api/src/app.ts @@ -0,0 +1,141 @@ +import path from 'path'; + +import cors from 'cors'; +import express, { NextFunction, Request, Response } from 'express'; +import helmet from 'helmet'; +import hpp from 'hpp'; +import morgan from 'morgan'; +import compression from 'compression'; +import session from 'express-session'; +import passport from 'passport'; + +import DB from './database'; +import { dbURI, sessionConfig, buildVersion } from './config'; +import { logger, stream } from './utils/logger'; + +import Routes from './interfaces/routes.interface'; + +import errorMiddleware from './middlewares/error.middleware'; +import AuthenticationRouter from './routes/authentication.routes'; +import AuthenticationMiddleware from './middlewares/auth.middleware'; +import authSetup from './auth'; +import HttpException from './exceptions/HttpException'; + +export default class App { + app: express.Application; + port: number; + env: string; + db: DB; + authMiddleWare: AuthenticationMiddleware; + serverReady: Promise; + constructor(port: number, env: string, routes: Routes[]) { + this.app = express(); + this.port = port; + this.env = env; + this.db = new DB(); + this.authMiddleWare = new AuthenticationMiddleware(); + this.serverReady = new Promise((resolve, reject) => { + const authPromise = authSetup(); + const dbPromise = this.db.initConnection(); + Promise.all([authPromise, dbPromise]) + .then(() => { + resolve(true); + }) + .catch(err => { + reject(err); + }); + }); + this.initMiddleWare(); + this.initRoutes(routes); + } + + listen() { + this.app + .listen(this.port, () => { + logger.info(`Started on Port ${this.port}`); + }) + .on('error', (err: any) => { + logger.error(err); + + logger.error(`Port ${this.port} is in use`); + }); + } + + getServer(): express.Application { + return this.app; + } + + cleanup() { + try { + console.error(`Cleaning up app`); + this.db.close(); + logger.destroy(); + } catch (error) { + console.error(`Failed to cleanup App: ${error}`); + } + } + + private initMiddleWare() { + this.app.set('Db', this.db); + + if (this.env === 'production') { + this.app.use(morgan('combined', { stream })); + this.app.use(cors({ origin: 'your.domain.com' })); + } else { + this.app.use(morgan('dev', { stream })); + this.app.use(cors({ origin: true })); + } + + this.app.set('trust proxy', 1); + + this.app.use(hpp()); + this.app.use(helmet()); + this.app.use(compression()); + this.app.use(express.json()); + const sc = sessionConfig; + + // eslint-disable-next-line @typescript-eslint/no-var-requires + const pgSession = require('connect-pg-simple')(session); + sc.setStore( + new pgSession({ + createTableIfMissing: true, + conString: dbURI, + errorLog: logger.error, + }), + ); + this.app.use(session(sc)); + this.app.use(passport.initialize()); + this.app.use(passport.session()); + this.app.use(express.urlencoded({ extended: true })); + this.app.use(this.authMiddleWare.userTokenAuthorization); + this.app.use(express.static(path.join(__dirname, 'client/'))); + } + + private initRoutes(routes: Routes[]) { + this.app.use(new AuthenticationRouter().router); + + this.app.get('/serverInfo', (req, res) => { + res.status(200).json({ buildVersion }); + }); + + routes.forEach(route => { + this.app.use(route.basePath!, this.authMiddleWare.authenticatedUser, route.router); + route.router.stack.forEach(value => { + // Handle straight middleware usage + if (!value || !value.route) { + return; + } + const keys = Object.keys(value.route.methods); + logger.info(`Registered Route at [${keys.join(',')}] ${value.route.path}`); + }); + }); + logger.info(`Registered Route for 404`); + this.app.get('/api', (_req: Request, _res: Response, next: NextFunction) => { + return next(new HttpException(404, 'Resource not found')); + }); + this.app.get('*', (_req: Request, res: Response) => { + return res.sendFile(path.join(__dirname, './client/index.html')); + }); + this.app.use(errorMiddleware); + } +} diff --git a/api/src/auth/index.ts b/api/src/auth/index.ts new file mode 100644 index 0000000..4a4d1a7 --- /dev/null +++ b/api/src/auth/index.ts @@ -0,0 +1,51 @@ +import passport from 'passport'; +import { generateStrategy } from './loginGov'; +import UserService from '../services/users.service'; +import { logger } from '../utils/logger'; +import { AppUser } from '../models/app_user'; +import { demoStrategy } from './token'; +import { LoginUserDetails } from '../interfaces/loginUser.interface'; + +const userService = new UserService(); + +passport.serializeUser((loginUserDetails: LoginUserDetails, done) => { + if (!loginUserDetails) { + logger.error('No User found'); + return done(new Error('No user')); + } + if (!loginUserDetails.hasUser) { + logger.debug(`All authorization should be done via headers - no user will be deserialized`); + return done(null, 'no-user'); + } + + if (loginUserDetails.id) { + logger.debug(`Serializing user ${loginUserDetails.email} - id: ${loginUserDetails.id}`); + return done(null, loginUserDetails.id); + } + logger.debug(`No user found to serialize`); + return done(new Error('No valid user types')); +}); + +passport.deserializeUser(async (id: string, done) => { + if (id === 'no-user') { + return done(null, null); + } + logger.info(`Deserializing user with: ${id}`); + try { + const user: AppUser = await userService.getUserById(id); + if (!user) { + throw new Error('There was an error retrieving your user, please reach out for support'); + } + logger.info(`User deserialized: ${user.email}`); + + return done(null, user); + } catch (err) { + done(err); + } +}); + +export default async function authSetup() { + const oidcLoa1Strat = await generateStrategy(); + passport.use('oidc-loa-1', oidcLoa1Strat); + passport.use('token-login', demoStrategy); +} diff --git a/api/src/auth/loginGov.ts b/api/src/auth/loginGov.ts new file mode 100644 index 0000000..0483816 --- /dev/null +++ b/api/src/auth/loginGov.ts @@ -0,0 +1,82 @@ +import crypto from 'crypto'; +import fs from 'fs'; + +import { Client, ClientMetadata, Issuer, Strategy } from 'openid-client'; +import jose from 'node-jose'; + +import { openIdConfig } from '../config'; +import { logger } from '../utils/logger'; +import { LoginUserDetails } from '../interfaces/loginUser.interface'; +import { AppUser } from '../models/app_user'; +import UserService from '../services/users.service'; +// const { KEYFILE } = require('../../constants') + +// const { devStrategy, demoStrategy } = require('./dev'); +const logoutObject: { issuer: Issuer | null } = { issuer: null }; +async function generateStrategy() { + const userService = new UserService(); + const secretKey = openIdConfig.secretKey; + const [key, issuer] = await Promise.all([jose.JWK.asKey(secretKey, 'pem'), Issuer.discover(openIdConfig.issuerDiscover)]); + logger.info(`Key successfully read`); + + logoutObject.issuer = issuer; + logger.info(`OpenId Issuer set to ${issuer.issuer}`); + + const clientOptions: ClientMetadata = { + client_id: openIdConfig.clientId, + token_endpoint_auth_method: 'private_key_jwt', + id_token_signed_response_alg: 'RS256', + }; + const params = { + response_type: 'code', + acr_values: 'http://idmanagement.gov/ns/assurance/ial/1', + scope: 'email', + redirect_uri: openIdConfig.redirectUri, + nonce: randomString(32), + state: randomString(32), + prompt: 'select_account', + }; + + // @ts-ignore + const client = new issuer.Client(clientOptions, key.keystore.toJSON(true)); + + // Because... clock drift is #real. + client.CLOCK_TOLERANCE = 5; + + // @ts-ignore + const oidcLoa1Strat = new Strategy({ client, params }, function (tokenset, userInfo, done) { + const { email, email_verified } = userInfo; + // sub, iss, id_token, id + if (!email_verified) { + logger.debug(`${email} is not verified with login.gov`); + return done(new Error('Email has not been verified; please verify with login.gov')); + } + return userService + .getUserByEmail(email.toLowerCase()) + .then((appUser: AppUser) => { + const { email, id, name } = appUser; + if (!id) { + return done(new Error('No user found'), null); + } + const user: LoginUserDetails = { + email, + id, + hasUser: true, + name, + }; + return done(null, user); + }) + .catch(err => { + logger.debug(`Login.gov error ${JSON.stringify(err)}`); + done(err); + }); + }); + return oidcLoa1Strat; +} + +function randomString(length: number) { + return crypto.randomBytes(length).toString('hex'); // source: https://github.com/18F/fs-permit-platform/blob/c613a73ae320980e226d301d0b34881f9d954758/server/src/util.es6#L232-L237 +} + +// devStrategy, demoStrategy, +export { generateStrategy, logoutObject }; diff --git a/api/src/auth/token.ts b/api/src/auth/token.ts new file mode 100644 index 0000000..27cf650 --- /dev/null +++ b/api/src/auth/token.ts @@ -0,0 +1,79 @@ +import { Request } from 'express'; +import { Strategy } from 'passport-custom'; +import { headerTokens } from '../config'; +import { LoginUserDetails } from '../interfaces/loginUser.interface'; +import UserService from '../services/users.service'; +import { logger } from '../utils/logger'; + +// @ts-ignore + +const demoStrategy = new Strategy(async function (req: Request, done) { + const bearerHeader = req.headers['authorization']; + const userService = new UserService(); + + logger.debug(`Received Token Header: ${bearerHeader}`); + + if (!headerTokens) { + logger.error(`Headers Tokens in configuration returned null for environment`); + return done(null, null); + } + + if (bearerHeader) { + const bearer = bearerHeader.split(' '); + const bearerToken = bearer[1]; + const user: LoginUserDetails = { + hasUser: true, + email: '', + name: 'token', + id: bearerToken, + }; + if (!Object.values(headerTokens).includes(bearerToken) || bearerToken === null) { + logger.warn(`Attempt to use invalid token ${bearerToken}`); + return done(null, null); + } + switch (bearerToken) { + case headerTokens.evaluator_one: + user.email = 'smeqa_evaluator_one@usds.gov'; + break; + case headerTokens.evaluator_two: + user.email = 'smeqa_evaluator_two@usds.gov'; + break; + case headerTokens.evaluator_three: + user.email = 'smeqa_evaluator_three@usds.gov'; + break; + case headerTokens.evaluator_four: + user.email = 'smeqa_evaluator_four@usds.gov'; + break; + case headerTokens.evaluator_five: + user.email = 'smeqa_evaluator_five@usds.gov'; + break; + case headerTokens.reviewer: + user.email = 'smeqa_reviewer@usds.gov'; + break; + case headerTokens.admin: + logger.debug(`Admin token`); + user.email = 'admin@usds.gov'; + user.hasUser = false; + break; + default: + throw new Error('Token not assigned email'); + } + + logger.debug(`Setting user as ${user.email}`); + + if (user.hasUser) { + user.id = (await userService.getUserByEmail(user.email)).id; + } + return done(null, user); + } else { + return done(null, null); + } +}); + +function isAdmin(bearerHeader: string) { + const bearer = bearerHeader.split(' '); + const bearerToken = bearer[1]; + return bearerToken === headerTokens.admin; +} + +export { demoStrategy, isAdmin }; diff --git a/api/src/config/index.ts b/api/src/config/index.ts new file mode 100644 index 0000000..8cbfdf1 --- /dev/null +++ b/api/src/config/index.ts @@ -0,0 +1,81 @@ +import path from 'path'; +import fs from 'fs'; +// const REDSREGEX = /"uri": "(redis:\/\/.*)"/; +const PSQLREGEX = /"uri": "(postgres:\/\/.*)"/; + +export interface OpenIDConfiguration { + issuerDiscover: string; + clientId: string; + redirectUri: string; + secretKey: string; +} + +export class SessionConfiguration { + secret: string; + name: string; + saveUninitialized: boolean; + resave: boolean; + cookie: { secure: boolean; httpOnly: boolean; maxAge: number }; + store: any | undefined; + + constructor(secret: string | undefined, name: string | undefined) { + this.secret = secret || 'big ole bunch of junk text'; + this.name = name || 'sess'; + this.resave = false; + this.saveUninitialized = true; + this.cookie = { secure: false, httpOnly: true, maxAge: 60000 * 60 * 24 }; + } + + public setStore(store: any) { + this.store = store; + } +} +let buildVersion = ''; +// for... reasons. +try { + const packageJSONBuffer = fs.readFileSync(path.join(__dirname, '../../package.json')); + const packageJSON = JSON.parse(packageJSONBuffer.toString()); + buildVersion = packageJSON.version || 'Version error'; +} catch (e) { + const packageJSONBuffer = fs.readFileSync(path.join(__dirname, '../package.json')); + const packageJSON = JSON.parse(packageJSONBuffer.toString()); + buildVersion = packageJSON.version || 'Version error'; +} + +// These users don't exist in prod - so :shrug: +// You _have_ to put in an admin token to populate your local DB. +const headerTokenString = { + evaluator_one: 'evaluator_one' + process.env.DEMO_TOKEN, + evaluator_two: 'evaluator_two' + process.env.DEMO_TOKEN, + evaluator_three: 'evaluator_three' + process.env.DEMO_TOKEN, + evaluator_four: 'evaluator_four' + process.env.DEMO_TOKEN, + evaluator_five: 'evaluator_five' + process.env.DEMO_TOKEN, + reviewer: 'reviewer' + process.env.DEMO_TOKEN, + admin: process.env?.ADMIN_TOKEN || null, +}; + +const env = process.env.APP_ENV?.toString() || 'development'; +// logger.info(`Database Connection Set to ${env}`); + +const dbURI: string = + process.env.POSTGRES_URI || + (process.env.VCAP_SERVICES && process.env.VCAP_SERVICES.match(PSQLREGEX)![1]) || + 'postgres://docker_pg_user:docker_pg_pw@docker_db:5432/docker_db'; + +const openIdConfig: OpenIDConfiguration = { + issuerDiscover: process.env.ISSUER_DISCOVER || 'https://idp.int.identitysandbox.gov/.well-known/openid-configuration', + clientId: process.env.CLIENT_ID || 'urn:gov:gsa:openidconnect.profiles:sp:sso:opm_usds:sme_qa', + redirectUri: process.env.REDIRECT_URI || 'http://localhost:9000/login/auth', + secretKey: process.env.LOGIN_KEY || '', +}; +const sessionConfig: SessionConfiguration = new SessionConfiguration(process.env?.SESSION_SECRET, process.env.NODE_ENV?.toString()); + +// @ts-ignore +const headerTokens: { [tokenName: string]: string } = headerTokenString; + +export { env }; +export { dbURI }; +export { openIdConfig }; +export { sessionConfig }; +export { headerTokens }; +export { buildVersion }; diff --git a/api/src/database/index.ts b/api/src/database/index.ts new file mode 100644 index 0000000..a8bd64f --- /dev/null +++ b/api/src/database/index.ts @@ -0,0 +1,99 @@ +import Sequelize, { QueryOptionsWithType, QueryTypes, Transaction } from 'sequelize'; +import { dbURI, env } from '../config'; +import { logger } from '../utils/logger'; +import { initModels } from '../models/init-models'; + +logger.info(`Database Connection for "${env}" set to ${dbURI.match(/@(.*)/)![0]}`); + +export interface DBInterface { + initConnection(): void; + transaction(autoCallback: (t: Transaction) => PromiseLike): Promise; + close(): void; + // eslint-disable-next-line @typescript-eslint/ban-types + query( + sql: string | { query: string; values: unknown[] }, + options: QueryOptionsWithType & { plain: true }, + ): Promise; + auditQuery(sql: string): void; +} + +export default class DB implements DBInterface { + public sequelize: Sequelize.Sequelize; + + constructor(autoInit = true) { + this.sequelize = new Sequelize.Sequelize(dbURI, { + pool: { + min: 0, + max: 30, + acquire: 30000, + idle: 3000, + }, + logQueryParameters: env === 'development', + logging: (query, time) => { + logger.debug(`${time}ms ${query}`); + }, + timezone: 'utc', + benchmark: true, + define: { + updatedAt: 'updated_at', + createdAt: 'created_at', + timestamps: false, + }, + }); + try { + initModels(this.sequelize); + } catch (err) { + logger.error(`DB initialization error ${JSON.stringify(err)}`); + } + if (autoInit) { + this.initConnection(); + } + } + + public initConnection() { + logger.info('Initializing DB connection.'); + return new Promise((resolve, reject) => { + this.sequelize + .authenticate() + .then(() => { + logger.info('🟢 The database is connected.'); + resolve(true); + }) + .catch((error: Error) => { + logger.error(`🔴 Unable to connect to the database: ${JSON.stringify(error)}.`); + reject(false); + }); + }); + } + + /** + * Wrapper around @see Sequelize.transaction + * @param autoCallback Callback for the transaction + */ + public async transaction(autoCallback: (t: Transaction) => PromiseLike): Promise { + return this.sequelize.transaction(autoCallback); + } + + /** + * Wrapper around @see Sequelize.query + * @param sql string + * @param options QueryOptionsWithType + */ + // eslint-disable-next-line @typescript-eslint/ban-types + public async query( + sql: string | { query: string; values: unknown[] }, + options: QueryOptionsWithType & { plain: true }, + ): Promise { + return this.sequelize.query(sql, options); + } + + public async auditQuery(sql: string): Promise { + this.sequelize.query(sql, { raw: true }); + return; + } + + public close() { + logger.error(`🔴 Closing DB.`); + return this.sequelize.close(); + } +} diff --git a/api/src/dto/BulkApplicantApplications.dto.ts b/api/src/dto/BulkApplicantApplications.dto.ts new file mode 100644 index 0000000..6bfa92b --- /dev/null +++ b/api/src/dto/BulkApplicantApplications.dto.ts @@ -0,0 +1,37 @@ +import { IsArray, IsNotEmpty, IsString, ValidateNested } from 'class-validator'; + +export default class BulkUSASApplicationsDto { + @IsArray() + @ValidateNested({ each: true }) + applications: USASApplicationDto[] = []; +} +export class USASApplicationDto { + @IsString() + @IsNotEmpty() + firstName = ''; + @IsString() + @IsNotEmpty() + lastName = ''; + @IsString() + middleName = ''; + // staffing applicationId + @IsString() + @IsNotEmpty() + staffingApplicationId = ''; + // staffing application number + @IsString() + @IsNotEmpty() + staffingApplicationNumber = ''; + // application rating Id + @IsString() + @IsNotEmpty() + staffingRatingId = ''; + // staffing assessment id + @IsString() + @IsNotEmpty() + staffingAssessmentId = ''; + // Staffing rating combination + @IsString() + @IsNotEmpty() + staffingRatingCombination = ''; +} diff --git a/api/src/dto/applicantFeedbackSubmit.dto.ts b/api/src/dto/applicantFeedbackSubmit.dto.ts new file mode 100644 index 0000000..63dcf25 --- /dev/null +++ b/api/src/dto/applicantFeedbackSubmit.dto.ts @@ -0,0 +1,6 @@ +import { IsString } from 'class-validator'; + +export class ApplicantFeedbackSubmitDto { + @IsString() + public feedback = ''; +} diff --git a/api/src/dto/applicantdisplay.dto.ts b/api/src/dto/applicantdisplay.dto.ts new file mode 100644 index 0000000..c6db895 --- /dev/null +++ b/api/src/dto/applicantdisplay.dto.ts @@ -0,0 +1,59 @@ +import { CompetencySelectors } from '../models/competency_selectors'; +import { Applicant } from '../models/applicant'; +import { Specialty } from '../models/specialty'; +import { ApplicationEvaluation } from '../models/application_evaluation'; + +export class CompetencyEvaluationDto { + competencyEvaluationId!: string; + evaluation_note?: string; + evaluator!: string; + competency_id!: string; + competency_selector_id!: string; + updated_at!: Date; +} +export class CompetencyJustification { + competencyEvaluationId!: string; + justification?: string; + evaluator!: string; + competency_id!: string; + competency_selector_id!: string; + updated_at!: Date; +} + +export class CompetencyWithSelectors { + id!: string; + name!: string; + local_id!: string; + assessment_hurdle_id!: string; + definition!: string; + required_proficiency_definition!: string; + display_type!: number; + screen_out!: boolean; + updated_at!: Date; + sort_order!: number; + selectors!: CompetencySelectors[]; +} + +type SpecialtyToCompetencyIds = { + [specialty: string]: string[]; +}; + +type CompetencyIdToEvaluation = { + [competencyId: string]: CompetencyEvaluationDto; +}; + +export default class ApplicantDisplayDto { + public applicant!: Applicant; + public applicantNotes!: string[] | null; + public feedback!: string; + public feedbackTimestamp!: Date; + // Really these should all be passed back as objects, but leaving it to keep the idiom the same. + public specialties!: Specialty[]; + public competencies!: CompetencyWithSelectors[]; + public competencyJustifications!: CompetencyJustification[]; + public specialtyCompetencyMap!: SpecialtyToCompetencyIds; + public applicationEvaluations!: ApplicationEvaluation[]; + public competencyEvaluations!: CompetencyIdToEvaluation; + public applicantEvaluationType!: string; + public isTieBreaker!: boolean; +} diff --git a/api/src/dto/applicantflag.dto.ts b/api/src/dto/applicantflag.dto.ts new file mode 100644 index 0000000..f60cdf6 --- /dev/null +++ b/api/src/dto/applicantflag.dto.ts @@ -0,0 +1,7 @@ +import { IsIn, IsNotEmpty, IsNumber, IsString, IsUUID } from 'class-validator'; + +export default class ApplicantFlagDto { + @IsString() + @IsNotEmpty() + public flagMessage = ''; +} diff --git a/api/src/dto/applicantmetrics.dto.ts b/api/src/dto/applicantmetrics.dto.ts new file mode 100644 index 0000000..7bd0f22 --- /dev/null +++ b/api/src/dto/applicantmetrics.dto.ts @@ -0,0 +1,31 @@ +import { IsNumber, IsRFC3339, IsString, IsUUID, Min } from 'class-validator'; + +export default class ApplicantMetricsDto { + constructor(params: Partial = {}) { + Object.assign(this, params); + } + + @IsUUID() + public applicantId = ''; + @IsUUID() + public applicationId = ''; + + @IsString() + public status = ''; + @IsNumber({ maxDecimalPlaces: 0 }) + @Min(0) + public passCount = 0; + @IsNumber({ maxDecimalPlaces: 0 }) + @Min(0) + public failCount = 0; + + public isRecused = false; + public isReturned = false; + @IsNumber({ maxDecimalPlaces: 0 }) + @Min(0) + public flagStatus = 0; + @IsString() + public message = ''; + @IsRFC3339() + public dataCreated = ''; +} diff --git a/api/src/dto/applicationresult.dto.ts b/api/src/dto/applicationresult.dto.ts new file mode 100644 index 0000000..1ada2c7 --- /dev/null +++ b/api/src/dto/applicationresult.dto.ts @@ -0,0 +1,15 @@ +import { IsString } from 'class-validator'; + +export default class ApplicationResultDto { + constructor(params: Partial = {}) { + Object.assign(this, params); + } + @IsString() + public assessmentHurdleId = ''; + @IsString() + public applicantId = ''; + @IsString() + public applicantName = ''; + @IsString() + public finalScore = ''; +} diff --git a/api/src/dto/applicationresultsusas.dto.ts b/api/src/dto/applicationresultsusas.dto.ts new file mode 100644 index 0000000..e8fe3b1b --- /dev/null +++ b/api/src/dto/applicationresultsusas.dto.ts @@ -0,0 +1,32 @@ +import { IsNumber, IsOptional, IsString } from 'class-validator'; + +export default class ApplicationResultUSASDto { + constructor(params: Partial = {}) { + Object.assign(this, params); + } + + @IsString() + public vacancyId = ''; + @IsString() + public assessmentId = ''; + @IsString() + public applicationId = ''; + @IsString() + public applicationRatingId = ''; + @IsString() + public applicantLastName = ''; + @IsString() + public applicantFirstName = ''; + @IsString() + public applicantMiddleName = ''; + @IsString() + public applicationNumber = ''; + @IsString() + public ratingCombination = ''; + @IsNumber() + @IsOptional() + public assessmentRating: number | null = null; + @IsString() + @IsOptional() + public minQualificationsRating: string | undefined = undefined; +} diff --git a/api/src/dto/auditfile.dto.ts b/api/src/dto/auditfile.dto.ts new file mode 100644 index 0000000..ee7c48d --- /dev/null +++ b/api/src/dto/auditfile.dto.ts @@ -0,0 +1,32 @@ +import { IsBoolean, IsEmail, IsNumber, IsRFC3339, IsString, IsUUID } from 'class-validator'; + +export default class AuditFileDto { + @IsUUID() + public assessmentHurdleId = ''; + + @IsUUID() + public applicationId = ''; + @IsUUID() + public specialtyId = ''; + @IsString() + public applicantName = ''; + @IsString() + public applicantId = ''; + + @IsNumber() + public flagType = 0; + + @IsEmail() + public evaluatorEmail = ''; + @IsString() + public evaluatorNote = ''; + + [competencyEvaluations: string]: any; + + @IsString() + public approverEmail = ''; + @IsBoolean() + public approved = false; + @IsRFC3339() + public timestamp = ''; +} diff --git a/api/src/dto/createapplicant.dto.ts b/api/src/dto/createapplicant.dto.ts new file mode 100644 index 0000000..dc941e3 --- /dev/null +++ b/api/src/dto/createapplicant.dto.ts @@ -0,0 +1,43 @@ +import { IsArray, IsIn, IsNotEmpty, IsNumber, IsOptional, IsString, IsUUID, MaxLength, ValidateNested } from 'class-validator'; + +export class CreateApplicantBulkDto { + @IsArray() + @ValidateNested({ each: true }) + public applicants: CreateApplicantDto[] = []; +} + +export default class CreateApplicantDto { + @IsUUID() + @IsOptional() + public existingId: string | undefined = undefined; + @IsString() + @IsNotEmpty() + public name = ''; + + @IsNumber() + @IsIn([0, 1, 2, 3]) //TODO should this be a Enum? + @IsOptional() + public flagType = 0; + @IsString() + @IsOptional() + public flagMessage = ''; + @IsUUID() + @IsOptional() + public assessmentHurdleId: string | undefined = undefined; + @IsString() + @MaxLength(1500) + public additionalNote = ''; + + // #region USAS Optional fields + @IsString() + public firstName = ''; + @IsString() + public middleName = ''; + @IsString() + public lastName = ''; + @IsString() + public applicationNumber: string | undefined = undefined; + @IsString() + public applicationId = ''; + // #endregion +} diff --git a/api/src/dto/createapplication.dto.ts b/api/src/dto/createapplication.dto.ts new file mode 100644 index 0000000..28a42f2 --- /dev/null +++ b/api/src/dto/createapplication.dto.ts @@ -0,0 +1,21 @@ +import { IsOptional, IsString, IsUUID } from 'class-validator'; + +export default class CreateApplicationDto { + @IsUUID() + @IsOptional() + public existingId: string | undefined = undefined; + @IsUUID() + public applicantId = ''; + @IsUUID() + public specialtyId = ''; + // #region USAS optional fields + @IsString() + public applicationMetaId = ''; + @IsString() + public applicationMetaRatingId = ''; + @IsString() + public applicationMetaAssessmentId = ''; + @IsString() + public applicationMetaRatingCombination = ''; + // #endregion +} diff --git a/api/src/dto/createapplicationassignment.dto.ts b/api/src/dto/createapplicationassignment.dto.ts new file mode 100644 index 0000000..a5fa727 --- /dev/null +++ b/api/src/dto/createapplicationassignment.dto.ts @@ -0,0 +1,18 @@ +import { IsBoolean, IsOptional, IsUUID } from 'class-validator'; + +export default class CreateApplicationAssignmentDto { + @IsUUID() + @IsOptional() + public existingId: string | undefined = undefined; + + @IsUUID() + @IsOptional() + public assessmentHurdleId: string | undefined = undefined; + + @IsUUID() + public evaluatorId = ''; + @IsUUID() + public applicantId = ''; + @IsBoolean() + public active = false; +} diff --git a/api/src/dto/createapplicationmapping.dto.ts b/api/src/dto/createapplicationmapping.dto.ts new file mode 100644 index 0000000..ff59ca6 --- /dev/null +++ b/api/src/dto/createapplicationmapping.dto.ts @@ -0,0 +1,8 @@ +import { IsUUID } from 'class-validator'; + +export default class CreateApplicationSpecialtyMappingDto { + @IsUUID() + public applicationId = ''; + @IsUUID() + public specialtyId = ''; +} diff --git a/api/src/dto/createassessmenthurdle.dto.ts b/api/src/dto/createassessmenthurdle.dto.ts new file mode 100644 index 0000000..0296fe5 --- /dev/null +++ b/api/src/dto/createassessmenthurdle.dto.ts @@ -0,0 +1,52 @@ +import { IsEmail, IsIn, IsNotEmpty, IsNumber, IsOptional, IsRFC3339, IsString, IsUUID, Min, MinLength } from 'class-validator'; + +export default class CreateAssessmentHurdleDto { + @IsUUID() + @IsOptional() + public existingId: string | undefined = undefined; + @IsString() + @IsNotEmpty() + public departmentName = ''; + @IsString() + @IsOptional() + public componentName: string | undefined = undefined; + @IsString() + @IsNotEmpty() + public positionName = ''; + @IsString() + @IsNotEmpty() + public positionDetails = ''; + @IsString() + @IsNotEmpty() + public locations = ''; + @IsRFC3339() + public startDatetime: Date = new Date(); + @IsRFC3339() + public endDatetime: Date = new Date(); + @IsNumber() + @IsIn([1, 2]) + public hurdleDisplayType = 1; + @IsNumber() + @Min(1) + public evaluationsRequired = 1; + @IsString() + @IsNotEmpty() + public hrName = ''; + @IsEmail() + public hrEmail = ''; + @IsString() + @IsNotEmpty() + public vacancyId = ''; + @IsString() + @IsNotEmpty() + public assessmentId = ''; + @IsString() + @IsNotEmpty() + public assessmentName = ''; + @IsString() + @IsOptional() + public passNor: string | undefined = undefined; + @IsString() + @IsOptional() + public failNor: string | undefined = undefined; +} diff --git a/api/src/dto/createcompetencies.dto.ts b/api/src/dto/createcompetencies.dto.ts new file mode 100644 index 0000000..bdb96e8 --- /dev/null +++ b/api/src/dto/createcompetencies.dto.ts @@ -0,0 +1,12 @@ +import { IsArray, IsOptional, IsUUID, ValidateNested } from 'class-validator'; +import CreateCompetencyDto from './createcompetency.dto'; + +export default class CreateCompetenciesDto { + @IsArray() + @ValidateNested({ each: true }) + public competencies: CreateCompetencyDto[] = []; + + @IsUUID() + @IsOptional() + public assessmentHurdleId: string | undefined = undefined; +} diff --git a/api/src/dto/createcompetency.dto.ts b/api/src/dto/createcompetency.dto.ts new file mode 100644 index 0000000..678e9d6 --- /dev/null +++ b/api/src/dto/createcompetency.dto.ts @@ -0,0 +1,46 @@ +import { IsString, IsNumber, IsUUID, IsBoolean, IsOptional, ValidateNested, MaxLength, MinLength, IsArray, IsNotEmpty } from 'class-validator'; +import CreateCompetencySelectorDto from './createcompselector.dto'; + +export default class CreateCompetencyDto { + @IsUUID() + @IsOptional() + public existingId: string | undefined = undefined; + + @IsUUID() + @IsOptional() + public assessmentHurdleId: string | undefined = undefined; + + @IsString() + @IsNotEmpty() + public name = ''; + + @IsString() + @IsNotEmpty() + public localId = ''; + + @IsString() + @MinLength(5) + @MaxLength(1500) + public definition = ''; + + @IsString() + @MaxLength(1500) + @MinLength(5) + public requiredProficiencyDefinition = ''; + @IsNumber() + public displayType = 0; + @IsBoolean() + public screenOut = false; + + @IsNumber() + public sortOrder = 0; + + @IsArray() + @IsOptional() + @ValidateNested({ each: true }) + public specialtyIds?: string[] | undefined = undefined; + + @IsArray() + @ValidateNested({ each: true }) + public selectors: CreateCompetencySelectorDto[] = []; +} diff --git a/api/src/dto/createcompselector.dto.ts b/api/src/dto/createcompselector.dto.ts new file mode 100644 index 0000000..98ba744 --- /dev/null +++ b/api/src/dto/createcompselector.dto.ts @@ -0,0 +1,20 @@ +import { IsNotEmpty, IsNumber, IsOptional, IsString, IsUUID, Min } from 'class-validator'; + +export default class CreateCompetencySelectorDto { + @IsUUID() + @IsOptional() + public existingId: string | undefined = undefined; + @IsUUID() + public competencyId = ''; + @IsString() + @IsNotEmpty() + public displayName = ''; + @IsNumber() + @Min(1) + public pointValue = 1; + @IsNumber() + public sortOrder = 0; + @IsString() + @IsNotEmpty() + public defaultText = ''; +} diff --git a/api/src/dto/createhurdleuser.dto.ts b/api/src/dto/createhurdleuser.dto.ts new file mode 100644 index 0000000..fee5cea --- /dev/null +++ b/api/src/dto/createhurdleuser.dto.ts @@ -0,0 +1,18 @@ +import { IsArray, IsIn, IsNumber, IsOptional, IsUUID, ValidateNested } from 'class-validator'; +import CreateUserDto from './createuser.dto'; + +export class CreateHurdleUserPairDto { + @IsNumber() + @IsIn([0, 1, 2, 10]) // TODO Enum? { admin: 0, hr: 1, sme: 2, all: 10} + public role = 0; + + @IsArray() + @ValidateNested({ each: true }) + public users: CreateUserDto[] = []; +} + +export default class CreateHurdleUserDto { + @IsArray() + @ValidateNested({ each: true }) + public userSetup: CreateHurdleUserPairDto[] = []; +} diff --git a/api/src/dto/createspecialties.dto.ts b/api/src/dto/createspecialties.dto.ts new file mode 100644 index 0000000..a06a071 --- /dev/null +++ b/api/src/dto/createspecialties.dto.ts @@ -0,0 +1,8 @@ +import { IsArray, ValidateNested } from 'class-validator'; +import CreateSpecialtyDto from './createspecialty.dto'; + +export default class CreateSpecialtiesDto { + @IsArray() + @ValidateNested({ each: true }) + specialties: CreateSpecialtyDto[] = []; +} diff --git a/api/src/dto/createspecialty.dto.ts b/api/src/dto/createspecialty.dto.ts new file mode 100644 index 0000000..dc65c2c --- /dev/null +++ b/api/src/dto/createspecialty.dto.ts @@ -0,0 +1,25 @@ +import { IsArray, IsNotEmpty, IsNumber, IsOptional, IsString, IsUUID } from 'class-validator'; + +export default class CreateSpecialtyDto { + @IsUUID() + @IsOptional() + public existingId: string | undefined = undefined; + @IsString() + @IsNotEmpty() + public name = ''; + @IsString() + @IsNotEmpty() + public localId = ''; + @IsUUID() + @IsOptional() + public assessmentHurdleId: string | undefined = undefined; + @IsNumber() + public pointsRequired = 1; + + @IsArray() + @IsOptional() + public competencyLocalIds?: string[] = []; + @IsArray() + @IsOptional() + public competencyIds?: string[] = []; +} diff --git a/api/src/dto/createuser.dto.ts b/api/src/dto/createuser.dto.ts new file mode 100644 index 0000000..cb515a4 --- /dev/null +++ b/api/src/dto/createuser.dto.ts @@ -0,0 +1,16 @@ +import { IsString, IsEmail, IsNotEmpty, MinLength } from 'class-validator'; + +export default class CreateUserDto { + constructor(email: string, name: string) { + this.email = email; + this.name = name; + } + + @IsEmail() + public email: string; + + @IsString() + @IsNotEmpty() + @MinLength(5) + public name: string; +} diff --git a/api/src/dto/evaluationcompetencysubmit.dto.ts b/api/src/dto/evaluationcompetencysubmit.dto.ts new file mode 100644 index 0000000..ba4d5f0 --- /dev/null +++ b/api/src/dto/evaluationcompetencysubmit.dto.ts @@ -0,0 +1,13 @@ +import { IsOptional, IsString, IsUUID, MaxLength, MinLength } from 'class-validator'; + +export default class EvaluationCompetencySubmitDto { + @IsUUID() + public competencyId = ''; + @IsUUID() + public selectorId = ''; + + @IsString() + @MaxLength(1500) + @IsOptional() + public note = ''; +} diff --git a/api/src/dto/evaluationfeedback.dto.ts b/api/src/dto/evaluationfeedback.dto.ts new file mode 100644 index 0000000..8a19a0f --- /dev/null +++ b/api/src/dto/evaluationfeedback.dto.ts @@ -0,0 +1,14 @@ +import { IsOptional, IsString, IsUUID, MaxLength, MinLength } from 'class-validator'; + +export class EvaluationApplicationFeedbackDto { + @IsUUID() + @IsOptional() + public existingId: string | undefined = undefined; + + @IsUUID() + public evaluatorId = ''; + + @IsString() + @MaxLength(1500) + public feedback = ''; +} diff --git a/api/src/dto/evaluationreviewsubmit.dto.ts b/api/src/dto/evaluationreviewsubmit.dto.ts new file mode 100644 index 0000000..ddd23c1 --- /dev/null +++ b/api/src/dto/evaluationreviewsubmit.dto.ts @@ -0,0 +1,8 @@ +import { IsArray, IsBoolean, IsOptional, IsString, IsUUID } from 'class-validator'; + +export class EvaluationApplicationReviewSubmitDto { + @IsArray() + public evaluationId: string[] = []; + @IsBoolean() + public review = false; +} diff --git a/api/src/dto/evaluationsubmit.dto.ts b/api/src/dto/evaluationsubmit.dto.ts new file mode 100644 index 0000000..35e3193 --- /dev/null +++ b/api/src/dto/evaluationsubmit.dto.ts @@ -0,0 +1,20 @@ +import { IsOptional, IsString, IsUUID, MaxLength, MinLength, IsBoolean, ValidateNested } from 'class-validator'; +import EvaluationCompetencySubmitDto from './evaluationcompetencysubmit.dto'; + +export default class EvaluationSubmitDto { + @IsOptional() + @IsString() + @MaxLength(1500) + public note: string | undefined = undefined; + + @IsString() + @IsOptional() + public evaluatorId = ''; + + @IsBoolean() + @IsOptional() + public isTieBreaker = false; + + @ValidateNested() + public competencyEvals: EvaluationCompetencySubmitDto[] = []; +} diff --git a/api/src/dto/flaggedapplicants.dto.ts b/api/src/dto/flaggedapplicants.dto.ts new file mode 100644 index 0000000..2f3f39e --- /dev/null +++ b/api/src/dto/flaggedapplicants.dto.ts @@ -0,0 +1,17 @@ +import { IsNumber, IsString, IsUUID } from 'class-validator'; + +export default class FlaggedApplicantsDto { + constructor(params: Partial = {}) { + Object.assign(this, params); + } + @IsUUID() + public assessmentHurdleId = ''; + @IsUUID() + public applicantId = ''; + @IsString() + public applicantName = ''; + @IsNumber() + public flagType = 0; + @IsString() + public flagMessage = ''; +} diff --git a/api/src/dto/hrdisplay.dto.ts b/api/src/dto/hrdisplay.dto.ts new file mode 100644 index 0000000..b003ed1 --- /dev/null +++ b/api/src/dto/hrdisplay.dto.ts @@ -0,0 +1,34 @@ +import { AssessmentHurdle } from '../models/assessment_hurdle'; +import { Applicant } from '../models/applicant'; + +export class CompetencyHrEvaluationDto { + evaluation_note?: string; + competency_name!: string; + competency_selector_name!: string; + sort_order!: number; + id!: string; +} + +export class ApplicantFeedbackDto { + applicantFeedbackId!: string; + evaluationFeedback!: string; +} +export class ApplicationHrEvaluationDto { + applicationIds!: string[]; + approved!: boolean; + feedback!: ApplicantFeedbackDto; + evaluation_note!: string; + evaluator!: string; + applicantName!: string; + applicantId!: string; + applicantEvaluationKey!: string; + created_at!: string; + competencyEvaluations!: CompetencyHrEvaluationDto[]; +} +export default class HrDisplayDto { + public assessmentHurdle!: AssessmentHurdle; + public flaggedApplicants!: Applicant[]; + public applicantEvaluations!: { + [applicantEvaluationKey: string]: ApplicationHrEvaluationDto; + }; +} diff --git a/api/src/dto/recusedapplicants.dto.ts b/api/src/dto/recusedapplicants.dto.ts new file mode 100644 index 0000000..05b6fbd --- /dev/null +++ b/api/src/dto/recusedapplicants.dto.ts @@ -0,0 +1,19 @@ +import { IsEmail, IsRFC3339, IsString, IsUUID } from 'class-validator'; + +export default class RecusedApplicantsDto { + constructor(params: Partial = {}) { + Object.assign(this, params); + } + @IsUUID() + public assessmentHurdleId = ''; + @IsUUID() + public applicantId = ''; + @IsString() + public applicantName = ''; + @IsUUID() + public recusedEvaluatorId = ''; + @IsEmail() + public recusedEvaluatorEmail = ''; + @IsRFC3339() + public timestamp = ''; +} diff --git a/api/src/exceptions/HttpException.ts b/api/src/exceptions/HttpException.ts new file mode 100644 index 0000000..87ec02f --- /dev/null +++ b/api/src/exceptions/HttpException.ts @@ -0,0 +1,10 @@ +export default class HttpException extends Error { + public status: number; + public message: string; + + constructor(status: number, message: string) { + super(message); + this.status = status; + this.message = message; + } +} diff --git a/api/src/handlers/admin.handler.ts b/api/src/handlers/admin.handler.ts new file mode 100644 index 0000000..631b8ef --- /dev/null +++ b/api/src/handlers/admin.handler.ts @@ -0,0 +1,18 @@ +import { NextFunction, Request, Response } from 'express'; +import DB from '../database'; +import AdminService from '../services/admin.service'; + +export default class AdminHandler { + service = new AdminService(); + + healthCheck = async (req: Request, res: Response, next: NextFunction) => { + const dbInstance = req.app.get('Db') as DB; + + try { + const rst = await this.service.doHealthCheck(dbInstance); + res.status(200).json({ data: rst, message: 'healthCheck' }); + } catch (error) { + next(error); + } + }; +} diff --git a/api/src/handlers/applicant.handler.ts b/api/src/handlers/applicant.handler.ts new file mode 100644 index 0000000..fa4f941 --- /dev/null +++ b/api/src/handlers/applicant.handler.ts @@ -0,0 +1,162 @@ +import path from 'path'; +import { NextFunction, Request, Response } from 'express'; +import ApplicantFlagDto from '../dto/applicantflag.dto'; +import CreateApplicantDto, { CreateApplicantBulkDto } from '../dto/createapplicant.dto'; +import { Applicant } from '../models/applicant'; +import { ApplicantRecusals } from '../models/applicant_recusals'; +import ApplicantService from '../services/applicant.service'; +import BulkUSASApplicationsDto from '../dto/BulkApplicantApplications.dto'; +import { logger } from '../utils/logger'; +import USASCsvReader from './util/usasCsvReader'; +import { validate as uuidValidate } from 'uuid'; +import HttpException from '../exceptions/HttpException'; +import DB from '../database'; + +export default class ApplicantHandler { + applicantSvc = new ApplicantService(); + + getAll = async (_req: Request, res: Response, next: NextFunction) => { + try { + const findAllUsersData: Applicant[] = await this.applicantSvc.getAll(); + res.status(200).json({ data: findAllUsersData, message: 'getAll' }); + } catch (error) { + next(error); + } + }; + + getById = async (req: Request, res: Response, next: NextFunction) => { + const { applicantId } = req.params; + if (!uuidValidate(applicantId)) { + return next(new HttpException(400, 'No, or invalid, applicant ID provided.')); + } + try { + const applicant: Applicant = await this.applicantSvc.getById(applicantId); + res.status(200).json({ data: applicant, message: 'getUserById' }); + } catch (error) { + next(error); + } + }; + + getByIdWithMeta = async (req: Request, res: Response, next: NextFunction) => { + const { assessmentHurdleId, applicantId } = req.params; + if (!uuidValidate(applicantId)) { + return next(new HttpException(400, 'No, or invalid, applicant ID provided.')); + } + if (!uuidValidate(assessmentHurdleId)) { + return next(new HttpException(400, 'No, or invalid, assessment hurdle ID provided.')); + } + try { + const rst = await this.applicantSvc.getByIdWithMeta(applicantId, assessmentHurdleId); + res.status(200).json({ data: rst, message: 'getByIdWithMeta' }); + } catch (error) { + next(error); + } + }; + + upsert = async (req: Request, res: Response, next: NextFunction) => { + const item: CreateApplicantDto = req.body; + const { assessmentHurdleId } = req.params; + if (!uuidValidate(assessmentHurdleId)) { + return next(new HttpException(400, 'No, or invalid, assessment hurdle ID provided.')); + } + try { + const created: Applicant = await this.applicantSvc.upsert(item, assessmentHurdleId); + res.status(201).json({ data: created, message: 'created' }); + } catch (error) { + next(error); + } + }; + + upsertBulk = async (req: Request, res: Response, next: NextFunction) => { + const item: CreateApplicantBulkDto = req.body; + const { assessmentHurdleId } = req.params; + if (!uuidValidate(assessmentHurdleId)) { + return next(new HttpException(400, 'No, or invalid, assessment hurdle ID provided.')); + } + try { + const created: Applicant[] = await this.applicantSvc.bulkUpsert(item, assessmentHurdleId); + res.status(201).json({ data: created, message: 'created' }); + } catch (error) { + next(error); + } + }; + + recuse = async (req: Request, res: Response, next: NextFunction) => { + const { applicantId } = req.params; + const { id: currentUserId } = req.user!; + + if (!uuidValidate(applicantId)) { + return next(new HttpException(400, 'No, or invalid, applicant ID provided.')); + } + if (!uuidValidate(currentUserId)) { + return next(new HttpException(500, 'User id invalid - this is a server error.')); + } + // try { + // const item: ApplicantRecusals = await this.applicantSvc.recuse(applicantId, currentUserId); + const dbInstance = req.app.settings['Db'] as DB; + + try { + const item: ApplicantRecusals = await this.applicantSvc.recuse(dbInstance, applicantId, currentUserId); + res.status(201).json({ data: item, message: 'created' }); + } catch (error) { + next(error); + } + }; + + flag = async (req: Request, res: Response, next: NextFunction) => { + const body: ApplicantFlagDto = req.body; + const flagMessage = body.flagMessage; + const applicantId = req.params.applicantId; + const userEmail = req.user!.email; + const userId = req.user!.id; + if (!uuidValidate(applicantId)) { + return next(new HttpException(400, 'No, or invalid, applicant ID provided.')); + } + if (!userEmail || !userId) { + return next(new HttpException(500, 'User invalid - this is a server error.')); + } + // TODO: check if user is in hiring action. + try { + const result: Applicant = await this.applicantSvc.flag(applicantId, 1, `${userEmail}: ${flagMessage}`, userId); + res.status(200).json({ data: result, message: 'flagged' }); + } catch (error) { + next(error); + } + }; + + importUSAS = async (req: Request, res: Response, next: NextFunction) => { + const { assessmentHurdleId } = req.params; + if (!uuidValidate(assessmentHurdleId)) { + return next(new HttpException(400, 'No, or invalid, assessment hurdle ID provided.')); + } + try { + // @ts-ignore + const filePath = req.files[0].path; + logger.debug(`Processing file at location ${path.resolve(filePath)}`); + const validatedBulkApplicants: BulkUSASApplicationsDto = await USASCsvReader.parseFile(filePath); + const created: Applicant[] = await this.applicantSvc.bulkUSASUpsert(validatedBulkApplicants, assessmentHurdleId); + res.status(201).json({ data: created, message: 'created' }); + } catch (error) { + next(error); + } + }; + + getDisplay = async (req: Request, res: Response, next: NextFunction) => { + const { applicantId } = req.params; + const { id: currentUserId } = req.user!; + if (!uuidValidate(applicantId)) { + return next(new HttpException(400, 'No, or invalid, applicant ID provided.')); + } + if (!uuidValidate(currentUserId)) { + return next(new HttpException(500, 'User id invalid - this is a server error.')); + } + try { + logger.info('passed validation'); + const rst = await this.applicantSvc.getDisplay(applicantId, currentUserId); + res.status(200).json({ data: rst, message: 'getDisplay' }); + } catch (error) { + logger.error('Error'); + next(error); + } + }; +} diff --git a/api/src/handlers/application.handler.ts b/api/src/handlers/application.handler.ts new file mode 100644 index 0000000..a56fd0c --- /dev/null +++ b/api/src/handlers/application.handler.ts @@ -0,0 +1,102 @@ +import { NextFunction, Request, Response } from 'express'; +import CreateApplicationDto from '../dto/createapplication.dto'; +import CreateApplicationSpecialtyMappingDto from '../dto/createapplicationmapping.dto'; +import HttpException from '../exceptions/HttpException'; +import { Application } from '../models/application'; +import ApplicationService from '../services/application.service'; +import { validate as uuidValidate } from 'uuid'; +export default class ApplicationHandler { + applicationSvc = new ApplicationService(); + + getById = async (req: Request, res: Response, next: NextFunction) => { + const { applicationId } = req.params; + if (!uuidValidate(applicationId)) { + return next(new HttpException(400, 'No, or invalid, application ID provided.')); + } + try { + const findAllUsersData: Application = await this.applicationSvc.getById(applicationId); + res.status(200).json({ data: findAllUsersData, message: 'getById' }); + } catch (error) { + next(error); + } + }; + + getByIdWithMeta = async (req: Request, res: Response, next: NextFunction) => { + const { applicationId } = req.params; + if (!applicationId) { + return next(new HttpException(400, 'Missing application id')); + } + try { + const [application, meta] = await this.applicationSvc.getByIdWithMeta(applicationId); + res.status(200).json({ data: { application, applicationMeta: meta }, message: 'getByIdWithMeta' }); + } catch (error) { + next(error); + } + }; + + getAllByApplicantId = async (req: Request, res: Response, next: NextFunction) => { + const { applicantId } = req.params; + if (!applicantId) { + return next(new HttpException(400, 'Missing applicantId id')); + } + try { + const findAllUsersData: Application[] = await this.applicationSvc.getAllByApplicantId(applicantId); + res.status(200).json({ data: findAllUsersData, message: 'getAllByApplicantId' }); + } catch (error) { + next(error); + } + }; + + getAllBySpecialtyId = async (req: Request, res: Response, next: NextFunction) => { + const { specialtyId } = req.params; + if (!specialtyId) { + return next(new HttpException(400, 'Missing specialty id')); + } + try { + const findAllUsersData: Application[] = await this.applicationSvc.getAllBySpecialtyId(specialtyId); + res.status(200).json({ data: findAllUsersData, message: 'getAllBySpecialtyId' }); + } catch (error) { + next(error); + } + }; + + upsert = async (req: Request, res: Response, next: NextFunction) => { + const body: CreateApplicationDto = req.body; + try { + const findAllUsersData: Application = await this.applicationSvc.upsert(body); + res.status(200).json({ data: findAllUsersData, message: 'upsert' }); + } catch (error) { + next(error); + } + }; + + createMapping = async (req: Request, res: Response, next: NextFunction) => { + const body: CreateApplicationSpecialtyMappingDto = req.body; + const { applicationId, specialtyId } = body; + if (!specialtyId) { + return next(new HttpException(400, 'Missing specialty id')); + } + if (!applicationId) { + return next(new HttpException(400, 'Missing applicationId id')); + } + + try { + const findAllUsersData: Application = await this.applicationSvc.createMapping(applicationId, specialtyId); + res.status(200).json({ data: findAllUsersData, message: 'createMapping' }); + } catch (error) { + next(error); + } + }; + + deleteMapping = async (req: Request, res: Response, next: NextFunction) => { + return next(new HttpException(500, 'Handler not implemented')); + const id = req.params.id; + + try { + const didDelete = await this.applicationSvc.deleteMappingById(id); + res.status(didDelete ? 200 : 500).json({ data: didDelete ? 'Ok' : 'Failed', message: 'deleteMappingById' }); + } catch (error) { + next(error); + } + }; +} diff --git a/api/src/handlers/applicationassignment.handler.ts b/api/src/handlers/applicationassignment.handler.ts new file mode 100644 index 0000000..adb25e5 --- /dev/null +++ b/api/src/handlers/applicationassignment.handler.ts @@ -0,0 +1,50 @@ +import { NextFunction, Request, Response } from 'express'; +import HttpException from '../exceptions/HttpException'; +import ApplicationAssignmentService from '../services/application_assignment.service'; + +export default class ApplicationAssignmentHandler { + applicationAssignmentSvc = new ApplicationAssignmentService(); + + getAll = async (req: Request, res: Response, next: NextFunction) => { + try { + const rst = await this.applicationAssignmentSvc.getAll(); + res.status(200).json({ data: rst, message: 'getAll' }); + } catch (error) { + next(error); + } + }; + + getByEvaluatorId = async (req: Request, res: Response, next: NextFunction) => { + const { assessmentHurdleId } = req.params; + const { id: currentUserId } = req.user!; + if (!assessmentHurdleId) { + return next(new HttpException(400, 'Missing or invalid assessment hurdle id')); + } + if (!currentUserId) { + return next(new HttpException(500, 'Missing current user')); + } + try { + const rst = await this.applicationAssignmentSvc.getByEvaluatorId(currentUserId, assessmentHurdleId); + res.status(200).json({ data: rst, message: 'getByEvaluatorId' }); + } catch (error) { + next(error); + } + }; + + nextAssignment = async (req: Request, res: Response, next: NextFunction) => { + const { assessmentHurdleId } = req.params; + const { id: currentUserId } = req.user!; + if (!assessmentHurdleId) { + return next(new HttpException(400, 'Missing or invalid assessment hurdle id')); + } + if (!currentUserId) { + return next(new HttpException(500, 'Missing current user')); + } + try { + const rst = await this.applicationAssignmentSvc.nextQueue(assessmentHurdleId, currentUserId); + res.status(200).json({ data: rst, message: 'nextAssignment' }); + } catch (error) { + next(error); + } + }; +} diff --git a/api/src/handlers/assessmenthurdle.handler.ts b/api/src/handlers/assessmenthurdle.handler.ts new file mode 100644 index 0000000..b461920 --- /dev/null +++ b/api/src/handlers/assessmenthurdle.handler.ts @@ -0,0 +1,73 @@ +import { NextFunction, Request, Response } from 'express'; +import AssessmentHurdleService from '../services/assessmenthurdle.service'; +import { AssessmentHurdle } from '../models/assessment_hurdle'; +import CreateAssessmentHurdleDto from '../dto/createassessmenthurdle.dto'; +import HttpException from '../exceptions/HttpException'; + +export default class AssessmentHurdleHandler { + assessmentHurdleSvc = new AssessmentHurdleService(); + + getAll = async (req: Request, res: Response, next: NextFunction) => { + try { + const findAllUsersData: AssessmentHurdle[] = await this.assessmentHurdleSvc.getAll(); + res.status(200).json({ data: findAllUsersData, message: 'getAll' }); + } catch (error) { + next(error); + } + }; + getAllForUser = async (req: Request, res: Response, next: NextFunction) => { + const { id: currentUserId } = req.user!; + if (!currentUserId) { + return next(new HttpException(500, 'Missing current user')); + } + + try { + // @ts-ignore + const findAllUsersData: AssessmentHurdle[] = await this.assessmentHurdleSvc.getAllWithUserRoles(currentUserId); + + res.status(200).json({ data: findAllUsersData, message: 'getAllForUser' }); + } catch (error) { + next(error); + } + }; + + getById = async (req: Request, res: Response, next: NextFunction) => { + const { assessmentHurdleId } = req.params; + if (!assessmentHurdleId) { + return next(new HttpException(400, 'Missing or invalid assessment hurdle id')); + } + try { + const findOneUserData: AssessmentHurdle = await this.assessmentHurdleSvc.getById(assessmentHurdleId); + res.status(200).json({ data: findOneUserData, message: 'getById' }); + } catch (error) { + next(error); + } + }; + + upsert = async (req: Request, res: Response, next: NextFunction) => { + const item: CreateAssessmentHurdleDto = req.body; + try { + const created: AssessmentHurdle = await this.assessmentHurdleSvc.upsert(item); + res.status(201).json({ data: created, message: 'created' }); + } catch (error) { + next(error); + } + }; + + getHrDisplay = async (req: Request, res: Response, next: NextFunction) => { + const { id: currentUserId } = req.user!; + const { assessmentHurdleId } = req.params; + if (!assessmentHurdleId) { + return next(new HttpException(400, 'Missing or invalid assessment hurdle id')); + } + if (!currentUserId) { + return next(new HttpException(500, 'Missing current user')); + } + try { + const rst = await this.assessmentHurdleSvc.getHrDisplay(assessmentHurdleId, currentUserId); + res.status(200).json({ data: rst, message: 'getHrDisplay' }); + } catch (error) { + next(error); + } + }; +} diff --git a/api/src/handlers/authentication.handler.ts b/api/src/handlers/authentication.handler.ts new file mode 100644 index 0000000..788a79c --- /dev/null +++ b/api/src/handlers/authentication.handler.ts @@ -0,0 +1,66 @@ +import { Request, Response } from 'express'; +import passport from 'passport'; +import { logger } from '../utils/logger'; + +export default class AuthenticationHandler { + loginUser = (req: Request, res: Response) => { + passport.authenticate('oidc-loa-1', (err, user, info) => { + logger.debug(`Login.gov info ${JSON.stringify(info)}`); + if (err) { + logger.error(`Login.gov error ${JSON.stringify(err)}`); + res.status(500).send({ data: null, message: 'There was an issue with login.gov or you are unauthorized to access this system.' }); + return; + } + if (!user) { + logger.debug('No user'); + res.redirect('/'); + return; + } + req.logIn(user, err => { + if (err) { + logger.error(`Error logging user in: ${JSON.stringify(err)}`); + res.status(500).send({ data: null, message: 'There was an issue with login.' }); + return; + } + logger.debug(`User Logging in: ${JSON.stringify(user)}`); + res.redirect('/'); + return; + }); + })(req, res); + }; + + checkUser = (req: Request, res: Response) => { + return res.status(200).send({ data: req.user || null, message: 'checkUser' }); + }; + logoutUser = (req: Request, res: Response) => { + req.logout(); + res.redirect('/'); + return; + }; + + tokenLogin = (req: Request, res: Response) => { + passport.authenticate('token-login', (err, user) => { + if (err) { + logger.error(`Error with token login ${JSON.stringify(err)}`); + res.status(500).send({ data: err, message: 'Error' }); + return; + } + + if (!user) { + logger.info('No user found for token login'); + res.status(404).send({ data: null, message: 'user not found' }); + } + + return req.logIn(user, err1 => { + if (err1) { + logger.error(`Error with token login ${JSON.stringify(err)}`); + res.status(500).send({ data: err1, message: 'Error' }); + return; + } + logger.debug(`User Logging in: ${JSON.stringify(user)}`); + res.status(200).send({ data: user.id, message: 'tokenLogin' }); + return; + }); + })(req, res); + }; +} diff --git a/api/src/handlers/competency.handler.ts b/api/src/handlers/competency.handler.ts new file mode 100644 index 0000000..fb2171b --- /dev/null +++ b/api/src/handlers/competency.handler.ts @@ -0,0 +1,81 @@ +import { NextFunction, Request, Response } from 'express'; +import CreateCompetenciesDto from '../dto/createcompetencies.dto'; +import CreateCompetencyDto from '../dto/createcompetency.dto'; +import HttpException from '../exceptions/HttpException'; +import { Competency } from '../models/competency'; +import CompetencyService from '../services/competency.service'; + +export default class CompetencyHandler { + competencySvc = new CompetencyService(); + getAll = async (_req: Request, res: Response, next: NextFunction) => { + try { + const findAllUsersData: Competency[] = await this.competencySvc.getAll(); + res.status(200).json({ data: findAllUsersData, message: 'getAll' }); + } catch (error) { + next(error); + } + }; + + getById = async (req: Request, res: Response, next: NextFunction) => { + const { competencyId } = req.params; + if (!competencyId) { + return next(new HttpException(400, 'Missing or invalid competency id')); + } + try { + const findOneUserData: Competency = await this.competencySvc.getById(competencyId); + res.status(200).json({ data: findOneUserData, message: 'getById' }); + } catch (error) { + next(error); + } + }; + + getAllMappingsById = async (req: Request, res: Response, next: NextFunction) => { + const { competencyId } = req.params; + if (!competencyId) { + return next(new HttpException(400, 'Missing or invalid competency id')); + } + try { + const findAll = await this.competencySvc.getAllMappingsById(competencyId); + res.status(200).json({ data: findAll, message: 'getAllMappingsById' }); + } catch (error) { + next(error); + } + }; + + upsert = async (req: Request, res: Response, next: NextFunction) => { + const item: CreateCompetencyDto = req.body; + let { assessmentHurdleId } = req.params; + if (!assessmentHurdleId) { + assessmentHurdleId = item.assessmentHurdleId!; + } + + if (!assessmentHurdleId) { + return next(new HttpException(400, 'Missing or invalid assessment hurdle id')); + } + + try { + const created: Competency = await this.competencySvc.upsert(item, assessmentHurdleId); + res.status(201).json({ data: created, message: 'created' }); + } catch (error) { + next(error); + } + }; + upsertAll = async (req: Request, res: Response, next: NextFunction) => { + const item: CreateCompetenciesDto = req.body; + + let { assessmentHurdleId } = req.params; + if (!assessmentHurdleId) { + assessmentHurdleId = item.assessmentHurdleId!; + } + + if (!assessmentHurdleId) { + return next(new HttpException(400, 'Missing or invalid assessment hurdle id')); + } + try { + const created: Competency[] = await this.competencySvc.upsertAll(item, assessmentHurdleId); + res.status(201).json({ data: created, message: 'created' }); + } catch (error) { + next(error); + } + }; +} diff --git a/api/src/handlers/evaluation.handler.ts b/api/src/handlers/evaluation.handler.ts new file mode 100644 index 0000000..10363b5 --- /dev/null +++ b/api/src/handlers/evaluation.handler.ts @@ -0,0 +1,75 @@ +import { NextFunction, Request, Response } from 'express'; +import DB from '../database'; +import { EvaluationApplicationFeedbackDto } from '../dto/evaluationfeedback.dto'; +import { EvaluationApplicationReviewSubmitDto } from '../dto/evaluationreviewsubmit.dto'; +import EvaluationSubmitDto from '../dto/evaluationsubmit.dto'; +import HttpException from '../exceptions/HttpException'; +import EvaluationService from '../services/evaluation.service'; + +export default class EvaluationHandler { + evaluationSvc = new EvaluationService(); + + submitApplicationReview = async (req: Request, res: Response, next: NextFunction) => { + const body: EvaluationApplicationReviewSubmitDto = req.body; + const { assessmentHurdleId } = req.params; + const { id: reviewerId } = req.user!; + const dbInstance = req.app.settings['Db'] as DB; + + if (!assessmentHurdleId) { + return next(new HttpException(400, 'Missing or invalid assessment hurdle id')); + } + if (!reviewerId) { + return next(new HttpException(500, 'Missing or invalid user')); + } + try { + const rst = await this.evaluationSvc.submitApplicationReview(dbInstance, body, reviewerId, assessmentHurdleId); + res.status(201).json({ data: rst, message: 'submitApplicationReview' }); + } catch (error) { + next(error); + } + }; + + submitApplicationFeedback = async (req: Request, res: Response, next: NextFunction) => { + const body: EvaluationApplicationFeedbackDto = req.body; + const { assessmentHurdleId, applicantId } = req.params; + const reviewerId = req.user!.id; + if (!assessmentHurdleId) { + return next(new HttpException(400, 'Missing or invalid assessment hurdle id')); + } + if (!applicantId) { + return next(new HttpException(400, 'Missing or invalid applicant id')); + } + if (!reviewerId) { + return next(new HttpException(500, 'Missing or invalid user')); + } + try { + const rst = await this.evaluationSvc.submitApplicationFeedback(body, assessmentHurdleId, applicantId, reviewerId); + res.status(201).json({ data: rst, message: 'submitApplicationFeedback' }); + } catch (error) { + next(error); + } + }; + + submitEvaluation = async (req: Request, res: Response, next: NextFunction) => { + const body: EvaluationSubmitDto = req.body; + + const { assessmentHurdleId, applicantId } = req.params; + const { id: evaluatorId } = req.user!; + + if (!applicantId) { + return next(new HttpException(400, 'Missing or invalid applicant id')); + } + if (!evaluatorId) { + return next(new HttpException(400, 'Missing or invalid applicant id')); + } + + const dbInstance = req.app.settings['Db'] as DB; + + try { + const rst = await this.evaluationSvc.submitEvaluation(dbInstance, body, assessmentHurdleId, applicantId, evaluatorId); + res.status(201).json({ data: rst, message: 'submitEvaluation' }); + } catch (error) { + next(error); + } + }; +} diff --git a/api/src/handlers/export.handler.ts b/api/src/handlers/export.handler.ts new file mode 100644 index 0000000..b0c17d2 --- /dev/null +++ b/api/src/handlers/export.handler.ts @@ -0,0 +1,173 @@ +import ExportService from '../services/export.service'; +import { NextFunction, Request, Response } from 'express'; +import { write } from 'fast-csv'; +import { validate as uuidValidate } from 'uuid'; +import HttpException from '../exceptions/HttpException'; + +export default class ExportHandler { + exportSvc = new ExportService(); + + getAuditFile = async (req: Request, res: Response, next: NextFunction) => { + const { assessmentHurdleId } = req.params; + + if (!assessmentHurdleId) { + return next(new HttpException(400, 'Missing or invalid assessment hurdle id')); + } + + try { + const evaluations = await this.exportSvc.getAuditFile(assessmentHurdleId); + const recused = await this.exportSvc.getRecusedApplicants(assessmentHurdleId); + const data = evaluations.concat(recused); + if (req.params.format && req.params.format === 'json') { + res.status(200).json({ data: data, message: 'getAuditFile' }); + } else { + res.header('Content-Type', 'text/csv'); + const fileName = `Audit-${assessmentHurdleId}.csv`; + res.attachment(fileName); + write(data, { + alwaysWriteHeaders: true, + quote: true, + writeHeaders: true, + quoteColumns: true, + quoteHeaders: false, + headers: Object.keys(data[0]), + }) + .pipe(res) + .on('end', () => { + res.end(); + }); + } + } catch (error) { + next(error); + } + }; + + getResultsFile = async (req: Request, res: Response, next: NextFunction) => { + const { assessmentHurdleId } = req.params; + if (!assessmentHurdleId) { + return next(new HttpException(400, 'Missing or invalid assessment hurdle id')); + } + + try { + const data = await this.exportSvc.getGeneralResult(assessmentHurdleId); + if (req.params.format && req.params.format === 'json') { + res.status(200).json({ data: data, message: 'getResultsFile' }); + } else { + res.header('Content-Type', 'text/csv'); + const fileName = `Results-${assessmentHurdleId}.csv`; + res.attachment(fileName); + write(data, { + alwaysWriteHeaders: true, + quote: true, + writeHeaders: true, + quoteColumns: true, + quoteHeaders: false, + headers: Object.keys(data[0]), + }).pipe(res); + } + } catch (error) { + next(error); + } + }; + + getUSASFile = async (req: Request, res: Response, next: NextFunction) => { + const { assessmentHurdleId } = req.params; + if (!assessmentHurdleId) { + return next(new HttpException(400, 'Missing or invalid assessment hurdle id')); + } + try { + const data = await this.exportSvc.getUSASResult(assessmentHurdleId); + if (req.params.format && req.params.format === 'json') { + res.status(200).json({ data: data, message: 'getResultsFile' }); + } else { + const formattedData = data.map(d => { + return { + 'Vacancy ID': d.vacancyId, + 'Assessment ID': d.assessmentId, + 'Application ID': d.applicationId, + 'Application Rating ID': d.applicationRatingId, + 'Applicant Last Name': d.applicantLastName, + 'Applicant First Name': d.applicantFirstName, + 'Applicant Middle Name': d.applicantMiddleName, + 'Application Number': d.applicationNumber, + 'Rating Combination': d.ratingCombination, + 'Assessment Rating': d.assessmentRating, + 'Minimum Qualifications Rating': d.minQualificationsRating, + }; + }); + + res.header('Content-Type', 'text/csv'); + const fileName = `Applicants-${assessmentHurdleId}.csv`; + res.attachment(fileName); + write(formattedData, { + alwaysWriteHeaders: true, + quote: true, + writeHeaders: true, + quoteColumns: true, + quoteHeaders: false, + headers: true, + }) + .pipe(res) + .on('end', () => { + res.end(); + }); + } + } catch (error) { + next(error); + } + }; + + // getRecusedFile = async (req: Request, res: Response, next: NextFunction) => { + // const { assessmentHurdleId } = req.params; + // if (!assessmentHurdleId) { + // return next(new HttpException(400, 'Missing or invalid assessment hurdle id')); + // } + // try { + // const data = await this.exportSvc.getRecusedApplicantsFile(assessmentHurdleId); + // if (req.params.format && req.params.format === 'json') { + // res.status(200).json({ data: data, message: 'getRecusedFile' }); + // } else { + // res.header('Content-Type', 'text/csv'); + // const fileName = `Recused-${assessmentHurdleId}.csv`; + // res.attachment(fileName); + // write(data, { + // alwaysWriteHeaders: true, + // quote: true, + // writeHeaders: true, + // quoteColumns: true, + // quoteHeaders: false, + // headers: Object.keys(data[0]), + // }).pipe(res); + // } + // } catch (error) { + // next(error); + // } + // }; + + // getFlaggedFile = async (req: Request, res: Response, next: NextFunction) => { + // const { assessmentHurdleId } = req.params; + // if (!assessmentHurdleId) { + // return next(new HttpException(400, 'Missing or invalid assessment hurdle id')); + // } + // try { + // const data = await this.exportSvc.getFlaggedApplicantsFile(assessmentHurdleId); + // if (req.params.format && req.params.format === 'json') { + // res.status(200).json({ data: data, message: 'getFlaggedFile' }); + // } else { + // res.header('Content-Type', 'text/csv'); + // const fileName = `Flagged-${assessmentHurdleId}.csv`; + // res.attachment(fileName); + // write(data, { + // alwaysWriteHeaders: true, + // quote: true, + // writeHeaders: true, + // quoteColumns: true, + // quoteHeaders: false, + // headers: Object.keys(data[0]), + // }).pipe(res); + // } + // } catch (error) { + // next(error); + // } + // }; +} diff --git a/api/src/handlers/metrics.handler.ts b/api/src/handlers/metrics.handler.ts new file mode 100644 index 0000000..e566d1b --- /dev/null +++ b/api/src/handlers/metrics.handler.ts @@ -0,0 +1,191 @@ +// import ExportService from '../services/export.service'; +import { NextFunction, Request, Response } from 'express'; +// import { write } from 'fast-csv'; +// import { validate as uuidValidate } from 'uuid'; +import HttpException from '../exceptions/HttpException'; +import MetricsService from '../services/metrics.service'; +import { logger } from '../utils/logger'; + +export default class MetricsHandler { + metricsSvc = new MetricsService(); + + getOverallStatus = async (req: Request, res: Response, next: NextFunction) => { + const { assessmentHurdleId } = req.params; + + if (!assessmentHurdleId) { + return next(new HttpException(400, 'Missing or invalid assessment hurdle id')); + } + try { + const results = await this.metricsSvc.getOverallMetrics(assessmentHurdleId); + return res.json(results); + } catch (err) { + logger.error(err); + } + }; + getEvaluatorProgress = async (req: Request, res: Response) => { + const { assessmentHurdleId } = req.params; + const evaluatorId = req.user!.id; + const evaluatorTotals = await this.metricsSvc.getEvaluatorTotals(assessmentHurdleId, evaluatorId); + + const applicantsEvaluatedByUser = evaluatorTotals + ? +evaluatorTotals.pending_review! + +evaluatorTotals.pending_amendment! + +evaluatorTotals.completed! + : 0; + const { totalEvaluations, totalEvaluationsNeeded, totalApplicants } = await this.metricsSvc.getHiringActionAggregates(assessmentHurdleId); + + return res.json({ + totalEvaluated: totalEvaluations, + totalEvaluationsNeeded, + applicantsEvaluatedByUser, + totalApplicants, + }); + }; + // getAuditFile = async (req: Request, res: Response, next: NextFunction) => { + // // const { assessmentHurdleId } = req.params; + // // if (!assessmentHurdleId) { + // // return next(new HttpException(400, 'Missing or invalid assessment hurdle id')); + // // } + // // try { + // // const data = await this.exportSvc.getAuditFile(assessmentHurdleId); + // // if (req.params.format && req.params.format === 'json') { + // // res.status(200).json({ data: data, message: 'getAuditFile' }); + // // } else { + // // res.header('Content-Type', 'text/csv'); + // // const fileName = `Audit-${assessmentHurdleId}.csv`; + // // res.attachment(fileName); + // // write(data, { + // // alwaysWriteHeaders: true, + // // quote: true, + // // writeHeaders: true, + // // quoteColumns: true, + // // quoteHeaders: false, + // // headers: Object.keys(data[0]), + // // }).pipe(res); + // // } + // // } catch (error) { + // // next(error); + // // } + // }; + + // getResultsFile = async (req: Request, res: Response, next: NextFunction) => { + // // const { assessmentHurdleId } = req.params; + // // if (!assessmentHurdleId) { + // // return next(new HttpException(400, 'Missing or invalid assessment hurdle id')); + // // } + // // try { + // // const data = await this.exportSvc.getGeneralResult(assessmentHurdleId); + // // if (req.params.format && req.params.format === 'json') { + // // res.status(200).json({ data: data, message: 'getResultsFile' }); + // // } else { + // // res.header('Content-Type', 'text/csv'); + // // const fileName = `Results-${assessmentHurdleId}.csv`; + // // res.attachment(fileName); + // // write(data, { + // // alwaysWriteHeaders: true, + // // quote: true, + // // writeHeaders: true, + // // quoteColumns: true, + // // quoteHeaders: false, + // // headers: Object.keys(data[0]), + // // }).pipe(res); + // // } + // // } catch (error) { + // // next(error); + // // } + // }; + + // getUSASFile = async (req: Request, res: Response, next: NextFunction) => { + // // const { assessmentHurdleId } = req.params; + // // if (!assessmentHurdleId) { + // // return next(new HttpException(400, 'Missing or invalid assessment hurdle id')); + // // } + // // try { + // // const data = await this.exportSvc.getUSASResult(assessmentHurdleId); + // // if (req.params.format && req.params.format === 'json') { + // // res.status(200).json({ data: data, message: 'getResultsFile' }); + // // } else { + // // const formattedData = data.map(d => { + // // return { + // // 'Vacancy ID': d.vacancyId, + // // 'Assessment ID': d.assessmentId, + // // 'Application ID': d.applicationId, + // // 'Application Rating ID': d.applicationRatingId, + // // 'Applicant Last Name': d.applicantLastName, + // // 'Applicant First Name': d.applicantFirstName, + // // 'Applicant Middle Name': d.applicantMiddleName, + // // 'Application Number': d.applicationNumber, + // // 'Rating Combination': d.ratingCombination, + // // 'Assessment Rating': d.assessmentRating, + // // 'Minimum Qualifications Rating': d.minQualificationsRating, + // // }; + // // }); + // // res.header('Content-Type', 'text/csv'); + // // const fileName = `Applicants-${assessmentHurdleId}.csv`; + // // res.attachment(fileName); + // // write(formattedData, { + // // alwaysWriteHeaders: true, + // // quote: true, + // // writeHeaders: true, + // // quoteColumns: true, + // // quoteHeaders: false, + // // headers: true, + // // }).pipe(res); + // // } + // // } catch (error) { + // // next(error); + // // } + // }; + + // getRecusedFile = async (req: Request, res: Response, next: NextFunction) => { + // // const { assessmentHurdleId } = req.params; + // // if (!assessmentHurdleId) { + // // return next(new HttpException(400, 'Missing or invalid assessment hurdle id')); + // // } + // // try { + // // const data = await this.exportSvc.getRecusedApplicantsFile(assessmentHurdleId); + // // if (req.params.format && req.params.format === 'json') { + // // res.status(200).json({ data: data, message: 'getRecusedFile' }); + // // } else { + // // res.header('Content-Type', 'text/csv'); + // // const fileName = `Recused-${assessmentHurdleId}.csv`; + // // res.attachment(fileName); + // // write(data, { + // // alwaysWriteHeaders: true, + // // quote: true, + // // writeHeaders: true, + // // quoteColumns: true, + // // quoteHeaders: false, + // // headers: Object.keys(data[0]), + // // }).pipe(res); + // // } + // // } catch (error) { + // // next(error); + // // } + // }; + + // getFlaggedFile = async (req: Request, res: Response, next: NextFunction) => { + // // const { assessmentHurdleId } = req.params; + // // if (!assessmentHurdleId) { + // // return next(new HttpException(400, 'Missing or invalid assessment hurdle id')); + // // } + // // try { + // // const data = await this.exportSvc.getFlaggedApplicantsFile(assessmentHurdleId); + // // if (req.params.format && req.params.format === 'json') { + // // res.status(200).json({ data: data, message: 'getFlaggedFile' }); + // // } else { + // // res.header('Content-Type', 'text/csv'); + // // const fileName = `Flagged-${assessmentHurdleId}.csv`; + // // res.attachment(fileName); + // // write(data, { + // // alwaysWriteHeaders: true, + // // quote: true, + // // writeHeaders: true, + // // quoteColumns: true, + // // quoteHeaders: false, + // // headers: Object.keys(data[0]), + // // }).pipe(res); + // // } + // // } catch (error) { + // // next(error); + // // } + // }; +} diff --git a/api/src/handlers/review.handler.ts b/api/src/handlers/review.handler.ts new file mode 100644 index 0000000..ef13954 --- /dev/null +++ b/api/src/handlers/review.handler.ts @@ -0,0 +1,43 @@ +import { NextFunction, Request, Response } from 'express'; +import { ApplicantFeedbackSubmitDto } from '../dto/applicantFeedbackSubmit.dto'; +import HttpException from '../exceptions/HttpException'; +import ReviewService from '../services/review.service'; + +export default class EvaluationHandler { + reviewSvc = new ReviewService(); + + submitApplicantFeedback = async (req: Request, res: Response, next: NextFunction) => { + const { feedback }: ApplicantFeedbackSubmitDto = req.body; + const { id: reviewerId } = req.user!; + const { assessmentHurdleId, applicantId } = req.params; + // is applicant in assessment hurdle? + + if (!assessmentHurdleId) { + return next(new HttpException(400, 'Missing or invalid assessment hurdle id')); + } + + try { + const rst = await this.reviewSvc.submitApplicantFeedback(assessmentHurdleId, applicantId, reviewerId, feedback!); + res.status(201).json({ data: rst, message: 'submitApplicantReview' }); + } catch (error) { + next(error); + } + }; + + releaseApplicant = async (req: Request, res: Response, next: NextFunction) => { + const { id: reviewerId } = req.user!; + const { assessmentHurdleId, applicantId } = req.params; + // is applicant in assessment hurdle? + + if (!assessmentHurdleId) { + return next(new HttpException(400, 'Missing or invalid assessment hurdle id')); + } + + try { + const rst = await this.reviewSvc.updateApplicantFlagStatus(assessmentHurdleId, applicantId, reviewerId, 0); + res.status(201).json({ data: rst, message: 'releaseApplicant' }); + } catch (error) { + next(error); + } + }; +} diff --git a/api/src/handlers/specialty.handler.ts b/api/src/handlers/specialty.handler.ts new file mode 100644 index 0000000..f209b03 --- /dev/null +++ b/api/src/handlers/specialty.handler.ts @@ -0,0 +1,79 @@ +import SpecialtyService from '../services/specialty.service'; +import { NextFunction, Request, Response } from 'express'; +import { Specialty } from '../models/specialty'; +import CreateSpecialtyDto from '../dto/createspecialty.dto'; +import CreateSpecialtiesDto from '../dto/createspecialties.dto'; +import HttpException from '../exceptions/HttpException'; + +export default class SpecialtyHandler { + specialtySvc = new SpecialtyService(); + + getAll = async (_req: Request, res: Response, next: NextFunction) => { + try { + const findAllUsersData: Specialty[] = await this.specialtySvc.getAll(); + res.status(200).json({ data: findAllUsersData, message: 'getAll' }); + } catch (error) { + next(error); + } + }; + + getById = async (req: Request, res: Response, next: NextFunction) => { + const { specialtyId } = req.params; + if (!specialtyId) { + return next(new HttpException(400, 'Missing or invalid specialty id')); + } + + try { + const findOneUserData: Specialty = await this.specialtySvc.getById(specialtyId); + res.status(200).json({ data: findOneUserData, message: 'getById' }); + } catch (error) { + next(error); + } + }; + + getAllMappingsById = async (req: Request, res: Response, next: NextFunction) => { + const { specialtyId, assessmentHurdleId } = req.params; + if (!assessmentHurdleId) { + return next(new HttpException(400, 'Missing or invalid assessment hurdle id')); + } + if (!specialtyId) { + return next(new HttpException(400, 'Missing or invalid specialty id')); + } + try { + const findAll = await this.specialtySvc.getAllMappingsById(specialtyId, assessmentHurdleId); + res.status(200).json({ data: findAll, message: 'getAllMappingsById' }); + } catch (error) { + next(error); + } + }; + + upsert = async (req: Request, res: Response, next: NextFunction) => { + const item: CreateSpecialtyDto = req.body; + if (!item.assessmentHurdleId) { + item.assessmentHurdleId = req.params.assessmentHurdleId; + } + if (!item.assessmentHurdleId) { + return next(new HttpException(400, 'Missing or invalid assessment hurdle id')); + } + try { + const created: Specialty = await this.specialtySvc.upsert(item); + res.status(201).json({ data: created, message: 'created' }); + } catch (error) { + next(error); + } + }; + + upsertAll = async (req: Request, res: Response, next: NextFunction) => { + const items: CreateSpecialtiesDto = req.body; + const { assessmentHurdleId } = req.params; + if (!assessmentHurdleId) { + return next(new HttpException(400, 'Missing or invalid assessment hurdle id')); + } + try { + const created = await this.specialtySvc.upsertAll(items, assessmentHurdleId); + res.status(201).json({ data: created, message: 'created' }); + } catch (error) { + next(error); + } + }; +} diff --git a/api/src/handlers/users.handler.ts b/api/src/handlers/users.handler.ts new file mode 100644 index 0000000..d3d52ab --- /dev/null +++ b/api/src/handlers/users.handler.ts @@ -0,0 +1,67 @@ +import { NextFunction, Request, Response } from 'express'; +import CreateHurdleUserDto from '../dto/createhurdleuser.dto'; +import CreateUserDto from '../dto/createuser.dto'; +import HttpException from '../exceptions/HttpException'; +import { AppUser } from '../models/app_user'; +import { AssessmentHurdleUser } from '../models/assessment_hurdle_user'; +import UserService from '../services/users.service'; + +export default class UsersHandler { + userService = new UserService(); + + getUsers = async (req: Request, res: Response, next: NextFunction) => { + try { + const findAllUsersData: AppUser[] = await this.userService.getAllUsers(); + res.status(200).json({ data: findAllUsersData, message: 'getAllUsers' }); + } catch (error) { + next(error); + } + }; + + getUserById = async (req: Request, res: Response, next: NextFunction) => { + const { userId } = req.params; + if (!userId) { + return next(new HttpException(400, 'Missing id for user')); + } + try { + const findOneUserData: AppUser = await this.userService.getUserById(userId); + res.status(200).json({ data: findOneUserData, message: 'getUserById' }); + } catch (error) { + next(error); + } + }; + + getUserByEmail = async (req: Request, res: Response, next: NextFunction) => { + const { userEmail } = req.params; + if (!userEmail) { + return next(new HttpException(400, 'Missing email for user')); + } + try { + const findOneUserData: AppUser = await this.userService.getUserByEmail(userEmail); + res.status(200).json({ data: findOneUserData, message: 'getUserByEmail' }); + } catch (error) { + next(error); + } + }; + + createUser = async (req: Request, res: Response, next: NextFunction) => { + const userData: CreateUserDto = req.body; + try { + const createUserData: AppUser = await this.userService.createUser(userData); + res.status(201).json({ data: createUserData, message: 'created' }); + } catch (error) { + next(error); + } + }; + + createUserAndAddToHurdle = async (req: Request, res: Response, next: NextFunction) => { + const userData: CreateHurdleUserDto = req.body; + const assessmentHurdleId = req.params.assessmentHurdleId; + try { + const created: AssessmentHurdleUser[] = await this.userService.createUserAndAddToHurdle(userData, assessmentHurdleId); + res.status(201).json({ data: created, message: 'created' }); + } catch (error) { + next(error); + } + }; +} diff --git a/api/src/handlers/util/usasCsvReader.ts b/api/src/handlers/util/usasCsvReader.ts new file mode 100644 index 0000000..1dcbb2d --- /dev/null +++ b/api/src/handlers/util/usasCsvReader.ts @@ -0,0 +1,56 @@ +import path from 'path'; +import fs from 'fs'; + +import { parse } from 'fast-csv'; +import { validateOrReject, ValidationError } from 'class-validator'; + +import BulkUSASApplicationsDto, { USASApplicationDto } from '../../dto/BulkApplicantApplications.dto'; +import { logger } from '../../utils/logger'; +import HttpException from '../../exceptions/HttpException'; + +export default class USASCsvReader { + static async parseFile(filePath: string): Promise { + return new Promise((resolve, reject) => { + const bulkApplications: BulkUSASApplicationsDto = new BulkUSASApplicationsDto(); + const fileRowPromise: Promise[] = []; + fs.createReadStream(path.resolve(filePath)) + .pipe(parse({ headers: true })) + .on('error', reject) + .on('data', (row: any) => { + if (!row || !row['Application Number']) { + logger.warn('Row empty or applicant without USAS number'); + return; + } + fileRowPromise.push( + new Promise((nestedResolve): void => { + const application = new USASApplicationDto(); + application.firstName = row['Applicant First Name']; + application.lastName = row['Applicant Last Name']; + application.middleName = row['Applicant Middle Name']; + application.staffingApplicationId = row['Application ID']; + application.staffingApplicationNumber = row['Application Number']; + application.staffingAssessmentId = row['Assessment ID']; + application.staffingRatingCombination = row['Rating Combination']; + application.staffingRatingId = row['Application Rating ID']; + bulkApplications.applications.push(application); + nestedResolve(null); + }), + ); + }) + .on('end', () => + Promise.all(fileRowPromise) + .then(async () => { + await validateOrReject(bulkApplications); + resolve(bulkApplications); + }) + .catch(errors => { + if (errors.length > 0) { + // @ts-ignore + const message = errors.map((error: ValidationError) => Object.values(error.constraints)).join(', '); + reject(new HttpException(400, message)); + } + }), + ); + }); + } +} diff --git a/api/src/interfaces/loginUser.interface.ts b/api/src/interfaces/loginUser.interface.ts new file mode 100644 index 0000000..6d45af1 --- /dev/null +++ b/api/src/interfaces/loginUser.interface.ts @@ -0,0 +1,9 @@ +export interface LoginUserDetails extends Express.User { + email: string; + // sub: string; + // iss?: string; + // id_token: string; + // state: string; + id: string; + hasUser?: boolean; +} diff --git a/api/src/interfaces/routes.interface.ts b/api/src/interfaces/routes.interface.ts new file mode 100644 index 0000000..a4f796f --- /dev/null +++ b/api/src/interfaces/routes.interface.ts @@ -0,0 +1,9 @@ +import { Router } from 'express'; + +interface Route { + basePath?: string; + path?: string; + router: Router; +} + +export default Route; diff --git a/api/src/middlewares/auth.middleware.ts b/api/src/middlewares/auth.middleware.ts new file mode 100644 index 0000000..e533b1c --- /dev/null +++ b/api/src/middlewares/auth.middleware.ts @@ -0,0 +1,115 @@ +import { NextFunction, Request, Response } from 'express'; +import passport from 'passport'; + +import AuthorizationService from '../services/authorization.service'; +import { logger } from '../utils/logger'; +import { isAdmin } from '../auth/token'; +import { validate as uuidValidate } from 'uuid'; +import HttpException from '../exceptions/HttpException'; +export default class AuthenticationMiddleware { + authorizationService = new AuthorizationService(); + userExists = (req: Request) => { + if (!req.user || !req.user.id) { + throw new HttpException(403, 'No user found'); + } + return; + }; + assessmentHurdleExists = (req: Request) => { + const { assessmentHurdleId } = req.params; + try { + uuidValidate(assessmentHurdleId); + } catch (_err) { + throw new HttpException(403, 'No assessment hurdle found'); + } + return; + }; + + authenticatedUser = (req: Request, _res: Response, next: NextFunction) => { + if (req.isAuthenticated()) { + return next(); + } + logger.info(`User is not authenticated: ${JSON.stringify(req.user)}`); + return next(new Error('User is not authenticated.')); + }; + + userTokenAuthorization = (req: Request, res: Response, next: NextFunction) => { + if (req.headers['authorization'] && req.headers['authorization'].length) { + logger.debug(`userTokenAuthorization - authorization header found: ${req.headers['authorization']}`); + passport.authenticate('token-login', function (err, user) { + if (err) { + return next(err); + } + if (user) { + return req.logIn(user, function (err) { + if (err) { + return next(err); + } + + next(); + }); + } + return next(); + })(req, res, next); + } else { + return next(); + } + }; + + authorizedUser = async (req: Request, _res: Response, next: NextFunction) => { + try { + const user = req.user!; + const { assessmentHurdleId } = req.params; + this.userExists(req); + this.assessmentHurdleExists(req); + logger.debug(`authorizedUser for ${user.id} in hurdle ${assessmentHurdleId}`); + const isAuthorized = await this.authorizationService.isAuthorizedForAssessmentHurdle(assessmentHurdleId, user?.id); + if (!isAuthorized) { + throw new Error('User not authorized for this route'); + } + return next(); + } catch (err) { + next(err); + } + }; + + authorizedEvaluator = async (req: Request, _res: Response, next: NextFunction) => { + try { + const user = req.user!; + const { assessmentHurdleId } = req.params; + logger.debug(`authorizedUserWithRole for ${user.id} in hurdle ${assessmentHurdleId}`); + + const isAuthorized = await this.authorizationService.isAuthorizedForRoleOnAssessmentHurdle(2, assessmentHurdleId, user?.id); + if (!isAuthorized) { + throw new Error('User not authorized for this route'); + } + return next(); + } catch (err) { + next(err); + } + }; + authorizedReviewer = async (req: Request, _res: Response, next: NextFunction) => { + try { + const user = req.user!; + const { assessmentHurdleId } = req.params; + logger.debug(`authorizedUserWithRole for ${user.id} in hurdle ${assessmentHurdleId}`); + const isAuthorized = await this.authorizationService.isAuthorizedForRoleOnAssessmentHurdle(1, assessmentHurdleId, user?.id); + if (!isAuthorized) { + throw new Error('User not authorized for this route'); + } + return next(); + } catch (err) { + next(err); + } + }; + + /** Admins are authorized for _all_ routes currently */ + authorizedAdminToken = (req: Request, _res: Response, next: NextFunction) => { + if (req.headers['authorization'] && req.headers['authorization'].length) { + if (isAdmin(req.headers['authorization'])) { + return next(); + } + return next(new HttpException(403, `Admin token invalid for route ${req.path}`)); + } + return next(new HttpException(401, `Admin token not found for route ${req.path}`)); + }; +} diff --git a/api/src/middlewares/error.middleware.ts b/api/src/middlewares/error.middleware.ts new file mode 100644 index 0000000..2ddab8c --- /dev/null +++ b/api/src/middlewares/error.middleware.ts @@ -0,0 +1,17 @@ +import { NextFunction, Request, Response } from 'express'; +import HttpException from '../exceptions/HttpException'; +import { logger } from '../utils/logger'; + +const errorMiddleware = (error: HttpException, _req: Request, res: Response, next: NextFunction) => { + try { + const status: number = error.status || 500; + const message: string = error.message || 'Something went wrong'; + + logger.error(`StatusCode : ${status}, Message : ${message}`); + res.status(status).json({ message }); + } catch (error) { + next(error); + } +}; + +export default errorMiddleware; diff --git a/api/src/middlewares/validator.middleware.ts b/api/src/middlewares/validator.middleware.ts new file mode 100644 index 0000000..c675329 --- /dev/null +++ b/api/src/middlewares/validator.middleware.ts @@ -0,0 +1,21 @@ +import { plainToClass } from 'class-transformer'; +import { validate, ValidationError } from 'class-validator'; +import { RequestHandler } from 'express'; +import HttpException from '../exceptions/HttpException'; + +const validationMiddleware = (type: any, value: string | 'body' | 'query' | 'params' = 'body', skipMissingProperties = false): RequestHandler => { + return (req, _res, next) => { + // @ts-ignore + validate(plainToClass(type, req[value]), { skipMissingProperties }).then((errors: ValidationError[]) => { + if (errors.length > 0) { + // @ts-ignore + const message = errors.map((error: ValidationError) => Object.values(error.constraints)).join(', '); + next(new HttpException(400, message)); + } else { + next(); + } + }); + }; +}; + +export default validationMiddleware; diff --git a/api/src/models/app_user.ts b/api/src/models/app_user.ts new file mode 100644 index 0000000..814951c --- /dev/null +++ b/api/src/models/app_user.ts @@ -0,0 +1,153 @@ +import Sequelize, { DataTypes, Model, Optional } from 'sequelize'; +import type { ApplicantRecusals, ApplicantRecusalsId } from './applicant_recusals'; +import type { ApplicationAssignments, ApplicationAssignmentsId } from './application_assignments'; +import type { ApplicationEvaluation, ApplicationEvaluationId } from './application_evaluation'; +import type { ApplicantEvaluationFeedback, ApplicantEvaluationFeedbackId } from './applicant_evaluation_feedback'; +import type { AssessmentHurdleUser, AssessmentHurdleUserId } from './assessment_hurdle_user'; +import type { CompetencyEvaluation, CompetencyEvaluationId } from './competency_evaluation'; + +export interface AppUserAttributes { + id: string; + email: string; + name?: string; + created_at?: Date; + updated_at?: Date; +} + +export type AppUserPk = 'id'; +export type AppUserId = AppUser[AppUserPk]; +export type AppUserCreationAttributes = Optional; + +export class AppUser extends Model implements AppUserAttributes { + id!: string; + email!: string; + name!: string; + created_at?: Date; + updated_at?: Date; + + // AppUser hasMany ApplicantRecusals + ApplicantRecusals!: ApplicantRecusals[]; + getApplicantRecusals!: Sequelize.HasManyGetAssociationsMixin; + setApplicantRecusals!: Sequelize.HasManySetAssociationsMixin; + addApplicantRecusal!: Sequelize.HasManyAddAssociationMixin; + addApplicantRecusals!: Sequelize.HasManyAddAssociationsMixin; + createApplicantRecusal!: Sequelize.HasManyCreateAssociationMixin; + removeApplicantRecusal!: Sequelize.HasManyRemoveAssociationMixin; + removeApplicantRecusals!: Sequelize.HasManyRemoveAssociationsMixin; + hasApplicantRecusal!: Sequelize.HasManyHasAssociationMixin; + hasApplicantRecusals!: Sequelize.HasManyHasAssociationsMixin; + countApplicantRecusals!: Sequelize.HasManyCountAssociationsMixin; + // AppUser hasMany ApplicationAssignments + ApplicationAssignments!: ApplicationAssignments[]; + getApplicationAssignments!: Sequelize.HasManyGetAssociationsMixin; + setApplicationAssignments!: Sequelize.HasManySetAssociationsMixin; + addApplicationAssignment!: Sequelize.HasManyAddAssociationMixin; + addApplicationAssignments!: Sequelize.HasManyAddAssociationsMixin; + createApplicationAssignment!: Sequelize.HasManyCreateAssociationMixin; + removeApplicationAssignment!: Sequelize.HasManyRemoveAssociationMixin; + removeApplicationAssignments!: Sequelize.HasManyRemoveAssociationsMixin; + hasApplicationAssignment!: Sequelize.HasManyHasAssociationMixin; + hasApplicationAssignments!: Sequelize.HasManyHasAssociationsMixin; + countApplicationAssignments!: Sequelize.HasManyCountAssociationsMixin; + // AppUser hasMany ApplicationEvaluation + ApplicationEvaluations!: ApplicationEvaluation[]; + getApplicationEvaluations!: Sequelize.HasManyGetAssociationsMixin; + setApplicationEvaluations!: Sequelize.HasManySetAssociationsMixin; + addApplicationEvaluation!: Sequelize.HasManyAddAssociationMixin; + addApplicationEvaluations!: Sequelize.HasManyAddAssociationsMixin; + createApplicationEvaluation!: Sequelize.HasManyCreateAssociationMixin; + removeApplicationEvaluation!: Sequelize.HasManyRemoveAssociationMixin; + removeApplicationEvaluations!: Sequelize.HasManyRemoveAssociationsMixin; + hasApplicationEvaluation!: Sequelize.HasManyHasAssociationMixin; + hasApplicationEvaluations!: Sequelize.HasManyHasAssociationsMixin; + countApplicationEvaluations!: Sequelize.HasManyCountAssociationsMixin; + // AppUser hasMany ApplicantEvaluationFeedback + ApplicantEvaluationFeedbacks!: ApplicantEvaluationFeedback[]; + getApplicantEvaluationFeedbacks!: Sequelize.HasManyGetAssociationsMixin; + setApplicantEvaluationFeedbacks!: Sequelize.HasManySetAssociationsMixin; + addApplicantEvaluationFeedback!: Sequelize.HasManyAddAssociationMixin; + addApplicantEvaluationFeedbacks!: Sequelize.HasManyAddAssociationsMixin; + createApplicantEvaluationFeedback!: Sequelize.HasManyCreateAssociationMixin; + removeApplicantEvaluationFeedback!: Sequelize.HasManyRemoveAssociationMixin; + removeApplicantEvaluationFeedbacks!: Sequelize.HasManyRemoveAssociationsMixin; + hasApplicantEvaluationFeedback!: Sequelize.HasManyHasAssociationMixin; + hasApplicantEvaluationFeedbacks!: Sequelize.HasManyHasAssociationsMixin; + countApplicantEvaluationFeedbacks!: Sequelize.HasManyCountAssociationsMixin; + + // AppUser hasMany AssessmentHurdleUser + AssessmentHurdleUsers!: AssessmentHurdleUser[]; + getAssessmentHurdleUsers!: Sequelize.HasManyGetAssociationsMixin; + setAssessmentHurdleUsers!: Sequelize.HasManySetAssociationsMixin; + addAssessmentHurdleUser!: Sequelize.HasManyAddAssociationMixin; + addAssessmentHurdleUsers!: Sequelize.HasManyAddAssociationsMixin; + createAssessmentHurdleUser!: Sequelize.HasManyCreateAssociationMixin; + removeAssessmentHurdleUser!: Sequelize.HasManyRemoveAssociationMixin; + removeAssessmentHurdleUsers!: Sequelize.HasManyRemoveAssociationsMixin; + hasAssessmentHurdleUser!: Sequelize.HasManyHasAssociationMixin; + hasAssessmentHurdleUsers!: Sequelize.HasManyHasAssociationsMixin; + countAssessmentHurdleUsers!: Sequelize.HasManyCountAssociationsMixin; + // AppUser hasMany CompetencyEvaluation + CompetencyEvaluations!: CompetencyEvaluation[]; + getCompetencyEvaluations!: Sequelize.HasManyGetAssociationsMixin; + setCompetencyEvaluations!: Sequelize.HasManySetAssociationsMixin; + addCompetencyEvaluation!: Sequelize.HasManyAddAssociationMixin; + addCompetencyEvaluations!: Sequelize.HasManyAddAssociationsMixin; + createCompetencyEvaluation!: Sequelize.HasManyCreateAssociationMixin; + removeCompetencyEvaluation!: Sequelize.HasManyRemoveAssociationMixin; + removeCompetencyEvaluations!: Sequelize.HasManyRemoveAssociationsMixin; + hasCompetencyEvaluation!: Sequelize.HasManyHasAssociationMixin; + hasCompetencyEvaluations!: Sequelize.HasManyHasAssociationsMixin; + countCompetencyEvaluations!: Sequelize.HasManyCountAssociationsMixin; + + static initModel(sequelize: Sequelize.Sequelize): typeof AppUser { + AppUser.init( + { + id: { + type: DataTypes.UUID, + allowNull: false, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + email: { + type: DataTypes.STRING, + allowNull: false, + unique: 'unique_email', + }, + name: { + type: DataTypes.STRING, + allowNull: true, + }, + created_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.fn('now'), + }, + updated_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.fn('now'), + }, + }, + { + sequelize, + tableName: 'app_user', + schema: 'public', + hasTrigger: true, + timestamps: false, + indexes: [ + { + name: 'app_user_pkey', + unique: true, + fields: [{ name: 'id' }], + }, + { + name: 'unique_email', + unique: true, + fields: [{ name: 'email' }], + }, + ], + }, + ); + return AppUser; + } +} diff --git a/api/src/models/applicant.ts b/api/src/models/applicant.ts new file mode 100644 index 0000000..44e54bd --- /dev/null +++ b/api/src/models/applicant.ts @@ -0,0 +1,148 @@ +import Sequelize, { DataTypes, Model, Optional } from 'sequelize'; +import type { ApplicantMeta, ApplicantMetaCreationAttributes, ApplicantMetaId } from './applicant_meta'; +import { ApplicantStatusMetrics } from './applicant_status_metrics'; +import type { ApplicantRecusals, ApplicantRecusalsId } from './applicant_recusals'; +import type { Application, ApplicationId } from './application'; +import type { ApplicationAssignments, ApplicationAssignmentsId } from './application_assignments'; +import type { AssessmentHurdle, AssessmentHurdleId } from './assessment_hurdle'; + +export interface ApplicantAttributes { + id: string; + name?: string; + flag_type?: number; + flag_message?: string; + assessment_hurdle_id?: string; + additional_note?: string; + created_at?: Date; + updated_at?: Date; +} + +export type ApplicantPk = 'id'; +export type ApplicantId = Applicant[ApplicantPk]; +export type ApplicantCreationAttributes = Optional; + +export class Applicant extends Model implements ApplicantAttributes { + id!: string; + name?: string; + flag_type?: number; + flag_message?: string; + assessment_hurdle_id?: string; + additional_note?: string; + created_at?: Date; + updated_at?: Date; + + // Applicant belongsTo AssessmentHurdle + AssessmentHurdle!: AssessmentHurdle; + getAssessmentHurdle!: Sequelize.BelongsToGetAssociationMixin; + setAssessmentHurdle!: Sequelize.BelongsToSetAssociationMixin; + createAssessmentHurdle!: Sequelize.BelongsToCreateAssociationMixin; + // Applicant hasOne ApplicantMeta + ApplicantMetum!: ApplicantMeta; + getApplicantMetum!: Sequelize.HasOneGetAssociationMixin; + setApplicantMetum!: Sequelize.HasOneSetAssociationMixin; + createApplicantMetum!: Sequelize.HasOneCreateAssociationMixin; + // Applicant hasMany ApplicantRecusals + ApplicantRecusals!: ApplicantRecusals[]; + getApplicantRecusals!: Sequelize.HasManyGetAssociationsMixin; + setApplicantRecusals!: Sequelize.HasManySetAssociationsMixin; + addApplicantRecusal!: Sequelize.HasManyAddAssociationMixin; + addApplicantRecusals!: Sequelize.HasManyAddAssociationsMixin; + createApplicantRecusal!: Sequelize.HasManyCreateAssociationMixin; + removeApplicantRecusal!: Sequelize.HasManyRemoveAssociationMixin; + removeApplicantRecusals!: Sequelize.HasManyRemoveAssociationsMixin; + hasApplicantRecusal!: Sequelize.HasManyHasAssociationMixin; + hasApplicantRecusals!: Sequelize.HasManyHasAssociationsMixin; + countApplicantRecusals!: Sequelize.HasManyCountAssociationsMixin; + // Applicant hasMany Application + Applications!: Application[]; + getApplications!: Sequelize.HasManyGetAssociationsMixin; + setApplications!: Sequelize.HasManySetAssociationsMixin; + addApplication!: Sequelize.HasManyAddAssociationMixin; + addApplications!: Sequelize.HasManyAddAssociationsMixin; + createApplication!: Sequelize.HasManyCreateAssociationMixin; + removeApplication!: Sequelize.HasManyRemoveAssociationMixin; + removeApplications!: Sequelize.HasManyRemoveAssociationsMixin; + hasApplication!: Sequelize.HasManyHasAssociationMixin; + hasApplications!: Sequelize.HasManyHasAssociationsMixin; + countApplications!: Sequelize.HasManyCountAssociationsMixin; + // Applicant hasMany ApplicationAssignments + ApplicationAssignments!: ApplicationAssignments[]; + getApplicationAssignments!: Sequelize.HasManyGetAssociationsMixin; + setApplicationAssignments!: Sequelize.HasManySetAssociationsMixin; + addApplicationAssignment!: Sequelize.HasManyAddAssociationMixin; + addApplicationAssignments!: Sequelize.HasManyAddAssociationsMixin; + createApplicationAssignment!: Sequelize.HasManyCreateAssociationMixin; + removeApplicationAssignment!: Sequelize.HasManyRemoveAssociationMixin; + removeApplicationAssignments!: Sequelize.HasManyRemoveAssociationsMixin; + hasApplicationAssignment!: Sequelize.HasManyHasAssociationMixin; + hasApplicationAssignments!: Sequelize.HasManyHasAssociationsMixin; + countApplicationAssignments!: Sequelize.HasManyCountAssociationsMixin; + + // Applicant hasOne ApplicantStatusMetrics + ApplicantStatusMetrics!: ApplicantStatusMetrics; + getApplicantStatusMetrics!: Sequelize.HasOneGetAssociationMixin; + + static initModel(sequelize: Sequelize.Sequelize): typeof Applicant { + Applicant.init( + { + id: { + type: DataTypes.UUID, + allowNull: false, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + name: { + type: DataTypes.STRING, + allowNull: true, + }, + flag_type: { + type: DataTypes.INTEGER, + allowNull: true, + defaultValue: 0, + }, + flag_message: { + type: DataTypes.STRING, + allowNull: true, + }, + assessment_hurdle_id: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'assessment_hurdle', + key: 'id', + }, + }, + additional_note: { + type: DataTypes.STRING(1500), + allowNull: true, + }, + created_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.fn('now'), + }, + updated_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.fn('now'), + }, + }, + { + sequelize, + tableName: 'applicant', + schema: 'public', + hasTrigger: true, + timestamps: false, + freezeTableName: true, + indexes: [ + { + name: 'applicant_pkey', + unique: true, + fields: [{ name: 'id' }], + }, + ], + }, + ); + return Applicant; + } +} diff --git a/api/src/models/applicant_application_evaluation_notes.ts b/api/src/models/applicant_application_evaluation_notes.ts new file mode 100644 index 0000000..55b6078 --- /dev/null +++ b/api/src/models/applicant_application_evaluation_notes.ts @@ -0,0 +1,33 @@ +import Sequelize, { DataTypes, Model } from 'sequelize'; + +export interface ApplicantApplicationEvaluationNotesAttributes { + evaluator?: string; + evaluation_note?: string; + applicant_id?: string; +} + +export type ApplicantApplicationEvaluationNotesCreationAttributes = ApplicantApplicationEvaluationNotesAttributes; + +export class ApplicantApplicationEvaluationNotes + extends Model + implements ApplicantApplicationEvaluationNotesAttributes { + evaluator?: string; + evaluation_note?: string; + applicant_id?: string; + static initModel(sequelize: Sequelize.Sequelize): typeof ApplicantApplicationEvaluationNotes { + ApplicantApplicationEvaluationNotes.init( + { + evaluator: { type: DataTypes.UUID, allowNull: true, primaryKey: true }, + applicant_id: { type: DataTypes.UUID, allowNull: true, primaryKey: true }, + evaluation_note: { type: DataTypes.STRING, allowNull: true }, + }, + { + sequelize, + tableName: 'applicant_application_evaluation_notes', + schema: 'public', + timestamps: false, + }, + ); + return ApplicantApplicationEvaluationNotes; + } +} diff --git a/api/src/models/applicant_evaluation_feedback.ts b/api/src/models/applicant_evaluation_feedback.ts new file mode 100644 index 0000000..2ca4abe --- /dev/null +++ b/api/src/models/applicant_evaluation_feedback.ts @@ -0,0 +1,113 @@ +import Sequelize, { DataTypes, Model, Optional } from 'sequelize'; +import type { AppUser, AppUserId } from './app_user'; + +export interface ApplicantEvaluationFeedbackAttributes { + id: string; + evaluation_feedback?: string; + applicant_id?: string; + evaluator_id?: string; + feedback_author_id?: string; + feedback_timestamp?: Date; + created_at?: Date; + updated_at?: Date; +} + +export type ApplicantEvaluationFeedbackPk = 'id'; +export type ApplicantEvaluationFeedbackId = ApplicantEvaluationFeedback[ApplicantEvaluationFeedbackPk]; +export type ApplicantEvaluationFeedbackCreationAttributes = Optional; + +export class ApplicantEvaluationFeedback + extends Model + implements ApplicantEvaluationFeedbackAttributes { + id!: string; + evaluation_feedback?: string; + applicant_id?: string; + evaluator_id?: string; + feedback_author_id?: string; + created_at?: Date; + updated_at?: Date; + + // ApplicantEvaluationFeedback belongsTo AppUser + AppUser!: AppUser; + getAppUser!: Sequelize.BelongsToGetAssociationMixin; + setAppUser!: Sequelize.BelongsToSetAssociationMixin; + createAppUser!: Sequelize.BelongsToCreateAssociationMixin; + + static initModel(sequelize: Sequelize.Sequelize): typeof ApplicantEvaluationFeedback { + ApplicantEvaluationFeedback.init( + { + id: { + type: DataTypes.UUID, + allowNull: false, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + evaluation_feedback: { + type: DataTypes.STRING(1500), + allowNull: true, + }, + applicant_id: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'app_user', + key: 'id', + }, + unique: 'unique_app_evaluation_feedback', + }, + evaluator_id: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'app_user', + key: 'id', + }, + unique: 'unique_app_evaluation_feedback', + }, + feedback_author_id: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'app_user', + key: 'id', + }, + unique: 'unique_app_evaluation_feedback', + }, + feedback_timestamp: { + type: DataTypes.DATE, + allowNull: true, + }, + created_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.fn('now'), + }, + updated_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.fn('now'), + }, + }, + { + sequelize, + tableName: 'applicant_evaluation_feedback', + schema: 'public', + hasTrigger: true, + timestamps: false, + indexes: [ + { + name: 'applicant_evaluation_feedback_pkey', + unique: true, + fields: [{ name: 'id' }], + }, + { + name: 'unique_app_evaluation_feedback', + unique: true, + fields: [{ name: 'applicant_id' }, { name: 'feedback_author_id' }, { name: 'evaluator_id' }], + }, + ], + }, + ); + return ApplicantEvaluationFeedback; + } +} diff --git a/api/src/models/applicant_meta.ts b/api/src/models/applicant_meta.ts new file mode 100644 index 0000000..5ec039c --- /dev/null +++ b/api/src/models/applicant_meta.ts @@ -0,0 +1,108 @@ +import Sequelize, { DataTypes, Model, Optional } from 'sequelize'; +import type { Applicant, ApplicantId } from './applicant'; + +export interface ApplicantMetaAttributes { + id: string; + staffing_first_name: string; + staffing_middle_name?: string; + staffing_last_name: string; + staffing_application_number?: string; + staffing_application_id: string; + applicant_id?: string; + created_at?: Date; + updated_at?: Date; +} + +export type ApplicantMetaPk = 'id'; +export type ApplicantMetaId = ApplicantMeta[ApplicantMetaPk]; +export type ApplicantMetaCreationAttributes = Optional; + +export class ApplicantMeta extends Model implements ApplicantMetaAttributes { + id!: string; + staffing_first_name!: string; + staffing_middle_name?: string; + staffing_last_name!: string; + staffing_application_number?: string; + staffing_application_id!: string; + applicant_id?: string; + created_at?: Date; + updated_at?: Date; + + // ApplicantMeta belongsTo Applicant + Applicant!: Applicant; + getApplicant!: Sequelize.BelongsToGetAssociationMixin; + setApplicant!: Sequelize.BelongsToSetAssociationMixin; + createApplicant!: Sequelize.BelongsToCreateAssociationMixin; + + static initModel(sequelize: Sequelize.Sequelize): typeof ApplicantMeta { + ApplicantMeta.init( + { + id: { + type: DataTypes.UUID, + allowNull: false, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + staffing_first_name: { + type: DataTypes.STRING, + allowNull: false, + }, + staffing_middle_name: { + type: DataTypes.STRING, + allowNull: true, + }, + staffing_last_name: { + type: DataTypes.STRING, + allowNull: false, + }, + staffing_application_number: { + type: DataTypes.STRING, + allowNull: true, + }, + staffing_application_id: { + type: DataTypes.STRING, + allowNull: false, + }, + applicant_id: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'applicant', + key: 'id', + }, + unique: 'unique_applicant_id_meta', + }, + created_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.fn('now'), + }, + updated_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.fn('now'), + }, + }, + { + sequelize, + tableName: 'applicant_meta', + schema: 'public', + hasTrigger: true, + timestamps: false, + indexes: [ + { + name: 'applicant_meta_pkey', + unique: true, + fields: [{ name: 'id' }], + }, + { + name: 'unique_applicant_id_meta', + unique: true, + fields: [{ name: 'applicant_id' }], + }, + ], + }, + ); + return ApplicantMeta; + } +} diff --git a/api/src/models/applicant_queue.ts b/api/src/models/applicant_queue.ts new file mode 100644 index 0000000..f5fbb7c --- /dev/null +++ b/api/src/models/applicant_queue.ts @@ -0,0 +1,45 @@ +import Sequelize, { DataTypes, Model } from 'sequelize'; +import { ApplicationEvaluation } from './application_evaluation'; + +export interface ApplicantQueueAttributes { + applicant_id?: string; + assessment_hurdle_id?: string; + evaluators?: number; +} + +export type ApplicantQueueCreationAttributes = ApplicantQueueAttributes; +export class ApplicantQueue extends Model implements ApplicantQueueAttributes { + applicant_id?: string; + assessment_hurdle_id?: string; + evaluators?: number; + + ApplicationEvaluation!: ApplicationEvaluation; + getApplicationEvaluation!: Sequelize.BelongsToGetAssociationMixin; + + static initModel(sequelize: Sequelize.Sequelize): typeof ApplicantQueue { + ApplicantQueue.init( + { + evaluators: { + type: DataTypes.BIGINT, + allowNull: true, + }, + applicant_id: { + type: DataTypes.UUID, + allowNull: true, + primaryKey: true, + }, + assessment_hurdle_id: { + type: DataTypes.UUID, + allowNull: true, + }, + }, + { + sequelize, + tableName: 'applicant_queue', + schema: 'public', + timestamps: false, + }, + ); + return ApplicantQueue; + } +} diff --git a/api/src/models/applicant_recusals.ts b/api/src/models/applicant_recusals.ts new file mode 100644 index 0000000..6e19954 --- /dev/null +++ b/api/src/models/applicant_recusals.ts @@ -0,0 +1,97 @@ +import Sequelize, { DataTypes, Model, Optional } from 'sequelize'; +import type { AppUser, AppUserId } from './app_user'; +import type { Applicant, ApplicantId } from './applicant'; + +export interface ApplicantRecusalsAttributes { + id: string; + applicant_id?: string; + recused_evaluator_id?: string; + created_at?: Date; + updated_at?: Date; +} + +export type ApplicantRecusalsPk = 'id'; +export type ApplicantRecusalsId = ApplicantRecusals[ApplicantRecusalsPk]; +export type ApplicantRecusalsCreationAttributes = Optional; + +export class ApplicantRecusals + extends Model + implements ApplicantRecusalsAttributes { + id!: string; + applicant_id?: string; + recused_evaluator_id?: string; + created_at?: Date; + updated_at?: Date; + + // ApplicantRecusals belongsTo Applicant + Applicant!: Applicant; + getApplicant!: Sequelize.BelongsToGetAssociationMixin; + setApplicant!: Sequelize.BelongsToSetAssociationMixin; + createApplicant!: Sequelize.BelongsToCreateAssociationMixin; + // ApplicantRecusals belongsTo AppUser + AppUser!: AppUser; + getAppUser!: Sequelize.BelongsToGetAssociationMixin; + setAppUser!: Sequelize.BelongsToSetAssociationMixin; + createAppUser!: Sequelize.BelongsToCreateAssociationMixin; + + static initModel(sequelize: Sequelize.Sequelize): typeof ApplicantRecusals { + ApplicantRecusals.init( + { + id: { + type: DataTypes.UUID, + allowNull: false, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + applicant_id: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'applicant', + key: 'id', + }, + unique: 'unique_combo_user', + }, + recused_evaluator_id: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'app_user', + key: 'id', + }, + unique: 'unique_combo_user', + }, + created_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.fn('now'), + }, + updated_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.fn('now'), + }, + }, + { + sequelize, + tableName: 'applicant_recusals', + schema: 'public', + hasTrigger: true, + timestamps: false, + indexes: [ + { + name: 'applicant_recusals_pkey', + unique: true, + fields: [{ name: 'id' }], + }, + { + name: 'unique_combo_user', + unique: true, + fields: [{ name: 'applicant_id' }, { name: 'recused_evaluator_id' }], + }, + ], + }, + ); + return ApplicantRecusals; + } +} diff --git a/api/src/models/applicant_status_metrics.ts b/api/src/models/applicant_status_metrics.ts new file mode 100644 index 0000000..40c3a8c --- /dev/null +++ b/api/src/models/applicant_status_metrics.ts @@ -0,0 +1,96 @@ +import Sequelize, { DataTypes, Model, Optional } from 'sequelize'; +import { Applicant } from './applicant'; +import { Application } from './application'; + +export type applicantEvaluationStatus = 'incomplete' | 'moves' | 'does not move'; +export type applicantReviewStatus = 'pending evaluations' | 'pending amendment' | 'pending review' | 'complete'; +export interface ApplicantStatusMetricsAttributes { + applicant_id?: string; + name?: string; + assessment_hurdle_id?: string; + evaluators?: number; + recused?: number; + flagged?: boolean; + evaluation_status?: string; + review_status?: string; + evaluations_required?: number; +} + +export type ApplicantStatusMetricsCreationAttributes = ApplicantStatusMetricsAttributes; + +export class ApplicantStatusMetrics + extends Model + implements ApplicantStatusMetricsAttributes +{ + applicant_id?: string; + name?: string; + assessment_hurdle_id?: string; + evaluators?: number; + recused?: number; + flagged?: boolean; + evaluation_status?: string; + review_status?: string; + evaluations_required?: number; + + Application!: Application; + getApplication!: Sequelize.BelongsToGetAssociationMixin; + + Applicant!: Applicant; + getApplicant!: Sequelize.BelongsToGetAssociationMixin; + + static initModel(sequelize: Sequelize.Sequelize): typeof ApplicantStatusMetrics { + ApplicantStatusMetrics.init( + { + evaluators: { + type: DataTypes.INTEGER, + allowNull: true, + }, + recused: { + type: DataTypes.INTEGER, + allowNull: true, + }, + flagged: { + type: DataTypes.BOOLEAN, + allowNull: true, + }, + evaluation_status: { + type: DataTypes.STRING, + allowNull: true, + }, + evaluations_required: { + type: DataTypes.STRING, + allowNull: true, + }, + name: { + type: DataTypes.STRING, + allowNull: true, + }, + review_status: { + type: DataTypes.STRING, + allowNull: true, + }, + applicant_id: { + type: DataTypes.UUID, + allowNull: false, + primaryKey: true, + references: { + model: 'applicant', + key: 'id', + }, + }, + assessment_hurdle_id: { + type: DataTypes.UUID, + allowNull: true, + }, + }, + { + sequelize, + tableName: 'applicant_status_metrics', + schema: 'public', + timestamps: false, + freezeTableName: true, + }, + ); + return ApplicantStatusMetrics; + } +} diff --git a/api/src/models/application.ts b/api/src/models/application.ts new file mode 100644 index 0000000..257f1f8 --- /dev/null +++ b/api/src/models/application.ts @@ -0,0 +1,114 @@ +import Sequelize, { DataTypes, Model, Optional } from 'sequelize'; +import type { Applicant, ApplicantId } from './applicant'; +import type { ApplicationEvaluation, ApplicationEvaluationId } from './application_evaluation'; +import type { ApplicationMeta, ApplicationMetaCreationAttributes, ApplicationMetaId } from './application_meta'; +import type { Specialty, SpecialtyId } from './specialty'; + +export interface ApplicationAttributes { + id: string; + applicant_id?: string; + specialty_id?: string; + created_at?: Date; + updated_at?: Date; +} + +export type ApplicationPk = 'id'; +export type ApplicationId = Application[ApplicationPk]; +export type ApplicationCreationAttributes = Optional; + +export class Application extends Model implements ApplicationAttributes { + id!: string; + applicant_id?: string; + specialty_id?: string; + created_at?: Date; + updated_at?: Date; + + // Application belongsTo Applicant + Applicant!: Applicant; + getApplicant!: Sequelize.BelongsToGetAssociationMixin; + setApplicant!: Sequelize.BelongsToSetAssociationMixin; + createApplicant!: Sequelize.BelongsToCreateAssociationMixin; + // Application belongsTo Specialty + Specialty!: Specialty; + getSpecialty!: Sequelize.BelongsToGetAssociationMixin; + setSpecialty!: Sequelize.BelongsToSetAssociationMixin; + createSpecialty!: Sequelize.BelongsToCreateAssociationMixin; + // Application hasMany ApplicationEvaluation + ApplicationEvaluations!: ApplicationEvaluation[]; + getApplicationEvaluations!: Sequelize.HasManyGetAssociationsMixin; + setApplicationEvaluations!: Sequelize.HasManySetAssociationsMixin; + addApplicationEvaluation!: Sequelize.HasManyAddAssociationMixin; + addApplicationEvaluations!: Sequelize.HasManyAddAssociationsMixin; + createApplicationEvaluation!: Sequelize.HasManyCreateAssociationMixin; + removeApplicationEvaluation!: Sequelize.HasManyRemoveAssociationMixin; + removeApplicationEvaluations!: Sequelize.HasManyRemoveAssociationsMixin; + hasApplicationEvaluation!: Sequelize.HasManyHasAssociationMixin; + hasApplicationEvaluations!: Sequelize.HasManyHasAssociationsMixin; + countApplicationEvaluations!: Sequelize.HasManyCountAssociationsMixin; + // Application hasOne ApplicationMeta + ApplicationMetum!: ApplicationMeta; + getApplicationMetum!: Sequelize.HasOneGetAssociationMixin; + setApplicationMetum!: Sequelize.HasOneSetAssociationMixin; + createApplicationMetum!: Sequelize.HasOneCreateAssociationMixin; + + static initModel(sequelize: Sequelize.Sequelize): typeof Application { + Application.init( + { + id: { + type: DataTypes.UUID, + allowNull: false, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + applicant_id: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'applicant', + key: 'id', + }, + unique: 'unique_pair', + }, + specialty_id: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'specialty', + key: 'id', + }, + unique: 'unique_pair', + }, + created_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.fn('now'), + }, + updated_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.fn('now'), + }, + }, + { + sequelize, + tableName: 'application', + schema: 'public', + hasTrigger: true, + timestamps: false, + indexes: [ + { + name: 'application_pkey', + unique: true, + fields: [{ name: 'id' }], + }, + { + name: 'unique_pair', + unique: true, + fields: [{ name: 'applicant_id' }, { name: 'specialty_id' }], + }, + ], + }, + ); + return Application; + } +} diff --git a/api/src/models/application_assignments.ts b/api/src/models/application_assignments.ts new file mode 100644 index 0000000..3608c5b --- /dev/null +++ b/api/src/models/application_assignments.ts @@ -0,0 +1,126 @@ +import Sequelize, { DataTypes, Model, Optional } from 'sequelize'; +import type { AppUser, AppUserId } from './app_user'; +import type { Applicant, ApplicantId } from './applicant'; +import { AssessmentHurdle, AssessmentHurdleId } from './assessment_hurdle'; + +export interface ApplicationAssignmentsAttributes { + id: string; + evaluator_id: string; + applicant_id: string; + assessment_hurdle_id: string; + expires?: Date; + active?: boolean; + created_at?: Date; + updated_at?: Date; +} + +export type ApplicationAssignmentsPk = 'id'; +export type ApplicationAssignmentsId = ApplicationAssignments[ApplicationAssignmentsPk]; +export type ApplicationAssignmentsCreationAttributes = Optional; + +export class ApplicationAssignments + extends Model + implements ApplicationAssignmentsAttributes { + id!: string; + evaluator_id!: string; + applicant_id!: string; + assessment_hurdle_id!: string; + active?: boolean; + created_at?: Date; + updated_at?: Date; + + // ApplicationAssignments belongsTo Applicant + Applicant!: Applicant; + getApplicant!: Sequelize.BelongsToGetAssociationMixin; + setApplicant!: Sequelize.BelongsToSetAssociationMixin; + createApplicant!: Sequelize.BelongsToCreateAssociationMixin; + // ApplicationAssignments belongsTo AppUser + AppUser!: AppUser; + getAppUser!: Sequelize.BelongsToGetAssociationMixin; + setAppUser!: Sequelize.BelongsToSetAssociationMixin; + createAppUser!: Sequelize.BelongsToCreateAssociationMixin; + // ApplicationAssignments belongsTo AssessmentHurdle + AssessmentHurdle!: AssessmentHurdle; + getAssessmentHurdle!: Sequelize.BelongsToGetAssociationMixin; + setAssessmentHurdle!: Sequelize.BelongsToSetAssociationMixin; + createAssessmentHurdle!: Sequelize.BelongsToCreateAssociationMixin; + + static initModel(sequelize: Sequelize.Sequelize): typeof ApplicationAssignments { + ApplicationAssignments.init( + { + id: { + type: DataTypes.UUID, + allowNull: false, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + evaluator_id: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'app_user', + key: 'id', + }, + unique: 'unique_combo_eval_app', + }, + applicant_id: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'applicant', + key: 'id', + }, + unique: 'unique_combo_eval_app', + }, + assessment_hurdle_id: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'assessment_hurdle', + key: 'id', + }, + unique: 'unique_combo_eval_app', + }, + active: { + type: DataTypes.BOOLEAN, + allowNull: true, + }, + expires: { + type: DataTypes.UUID, + allowNull: true, + defaultValue: Sequelize.fn('now'), + }, + created_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.fn('now'), + }, + updated_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.fn('now'), + }, + }, + { + sequelize, + tableName: 'application_assignments', + schema: 'public', + hasTrigger: true, + timestamps: false, + indexes: [ + { + name: 'application_assignments_pkey', + unique: true, + fields: [{ name: 'id' }], + }, + { + name: 'unique_combo_eval_app', + unique: true, + fields: [{ name: 'evaluator_id' }, { name: 'applicant_id' }, { name: 'assessment_hurdle_id' }], + }, + ], + }, + ); + return ApplicationAssignments; + } +} diff --git a/api/src/models/application_evaluation.ts b/api/src/models/application_evaluation.ts new file mode 100644 index 0000000..b65699c --- /dev/null +++ b/api/src/models/application_evaluation.ts @@ -0,0 +1,151 @@ +import Sequelize, { DataTypes, Model, Optional } from 'sequelize'; +import type { AppUser, AppUserId } from './app_user'; +import type { Application, ApplicationId } from './application'; +import type { ApplicationEvaluationCompetency, ApplicationEvaluationCompetencyId } from './application_evaluation_competency'; + +export interface ApplicationEvaluationAttributes { + id: string; + evaluation_note?: string; + evaluator?: string; + approved?: boolean | null; + approver_id?: string | null; + feedback_timestamp?: Date; + application_id?: string; + created_at?: Date; + updated_at?: Date; + approved_type?: number; +} + +export type ApplicationEvaluationPk = 'id'; +export type ApplicationEvaluationId = ApplicationEvaluation[ApplicationEvaluationPk]; +export type ApplicationEvaluationCreationAttributes = Optional; + +export const approvalTypes = { + reviewerApproval: 1, + automaticApproval: 2, +}; +export class ApplicationEvaluation + extends Model + implements ApplicationEvaluationAttributes { + id!: string; + evaluation_note?: string; + evaluator?: string; + approved?: boolean | null; + approver_id?: string | null; + feedback_timestamp?: Date; + application_id?: string; + created_at?: Date; + updated_at?: Date; + approved_type?: number; + + // ApplicationEvaluation belongsTo Application + Application!: Application; + getApplication!: Sequelize.BelongsToGetAssociationMixin; + setApplication!: Sequelize.BelongsToSetAssociationMixin; + createApplication!: Sequelize.BelongsToCreateAssociationMixin; + // ApplicationEvaluation belongsTo AppUser + Approver!: AppUser; + getApprover!: Sequelize.BelongsToGetAssociationMixin; + setApprover!: Sequelize.BelongsToSetAssociationMixin; + createApprover!: Sequelize.BelongsToCreateAssociationMixin; + Evaluator!: AppUser; + getEvaluator!: Sequelize.BelongsToGetAssociationMixin; + setEvaluator!: Sequelize.BelongsToSetAssociationMixin; + createEvaluator!: Sequelize.BelongsToCreateAssociationMixin; + // ApplicationEvaluation hasMany ApplicationEvaluationCompetency + ApplicationEvaluationCompetencies!: ApplicationEvaluationCompetency[]; + getApplicationEvaluationCompetencies!: Sequelize.HasManyGetAssociationsMixin; + setApplicationEvaluationCompetencies!: Sequelize.HasManySetAssociationsMixin; + addApplicationEvaluationCompetency!: Sequelize.HasManyAddAssociationMixin; + addApplicationEvaluationCompetencies!: Sequelize.HasManyAddAssociationsMixin; + createApplicationEvaluationCompetency!: Sequelize.HasManyCreateAssociationMixin; + removeApplicationEvaluationCompetency!: Sequelize.HasManyRemoveAssociationMixin; + removeApplicationEvaluationCompetencies!: Sequelize.HasManyRemoveAssociationsMixin< + ApplicationEvaluationCompetency, + ApplicationEvaluationCompetencyId + >; + hasApplicationEvaluationCompetency!: Sequelize.HasManyHasAssociationMixin; + hasApplicationEvaluationCompetencies!: Sequelize.HasManyHasAssociationsMixin; + countApplicationEvaluationCompetencies!: Sequelize.HasManyCountAssociationsMixin; + + static initModel(sequelize: Sequelize.Sequelize): typeof ApplicationEvaluation { + ApplicationEvaluation.init( + { + id: { + type: DataTypes.UUID, + allowNull: false, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + evaluation_note: { + type: DataTypes.STRING(1500), + allowNull: true, + defaultValue: 'NULL', + }, + evaluator: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'app_user', + key: 'id', + }, + }, + approved: { + type: DataTypes.BOOLEAN, + allowNull: true, + }, + approver_id: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'app_user', + key: 'id', + }, + }, + feedback_timestamp: { + type: DataTypes.DATE, + allowNull: true, + }, + application_id: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'application', + key: 'id', + }, + }, + created_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.fn('now'), + }, + updated_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.fn('now'), + }, + approved_type: { + type: DataTypes.NUMBER, + allowNull: true, + defaultValue: approvalTypes.reviewerApproval, + }, + }, + { + sequelize, + tableName: 'application_evaluation', + schema: 'public', + hasTrigger: true, + timestamps: false, + indexes: [ + { + name: 'application_evaluation_pkey', + unique: true, + fields: [{ name: 'id' }], + }, + { name: 'unique_application_evaluation', unique: true, fields: ['application_id', 'evaluator'] }, + ], + }, + ); + return ApplicationEvaluation; + } +} diff --git a/api/src/models/application_evaluation_agg.ts b/api/src/models/application_evaluation_agg.ts new file mode 100644 index 0000000..ffdd5d6 --- /dev/null +++ b/api/src/models/application_evaluation_agg.ts @@ -0,0 +1,93 @@ +import Sequelize, { DataTypes, Model } from 'sequelize'; + +export interface ApplicationEvaluationAggAttributes { + application_id?: string; + applicant_id?: string; + evaluations_required?: number; + does_not_meet?: number; + meets?: number; + exceeds?: number; + pending_amendment?: number; + pending_review?: number; + assessment_hurdle_id?: string; + complete?: number; + evaluations_remaining?: number; +} + +export type ApplicationEvaluationAggCreationAttributes = ApplicationEvaluationAggAttributes; + +export class ApplicationEvaluationAgg + extends Model + implements ApplicationEvaluationAggAttributes { + application_id?: string; + applicant_id?: string; + evaluations_required?: number; + does_not_meet?: number; + meets?: number; + exceeds?: number; + pending_amendment?: number; + pending_review?: number; + assessment_hurdle_id?: string; + complete?: number; + evaluations_remaining?: number; + + static initModel(sequelize: Sequelize.Sequelize): typeof ApplicationEvaluationAgg { + ApplicationEvaluationAgg.init( + { + application_id: { + type: DataTypes.UUID, + allowNull: true, + primaryKey: true, + }, + applicant_id: { + type: DataTypes.UUID, + allowNull: true, + }, + assessment_hurdle_id: { + type: DataTypes.UUID, + allowNull: true, + }, + evaluations_required: { + type: DataTypes.INTEGER, + allowNull: true, + }, + does_not_meet: { + type: DataTypes.BIGINT, + allowNull: true, + }, + meets: { + type: DataTypes.BIGINT, + allowNull: true, + }, + exceeds: { + type: DataTypes.BIGINT, + allowNull: true, + }, + pending_amendment: { + type: DataTypes.BIGINT, + allowNull: true, + }, + pending_review: { + type: DataTypes.BIGINT, + allowNull: true, + }, + complete: { + type: DataTypes.BIGINT, + allowNull: true, + }, + evaluations_remaining: { + type: DataTypes.BIGINT, + allowNull: true, + }, + }, + { + sequelize, + tableName: 'application_evaluation_agg', + schema: 'public', + timestamps: false, + freezeTableName: true, + }, + ); + return ApplicationEvaluationAgg; + } +} diff --git a/api/src/models/application_evaluation_competency.ts b/api/src/models/application_evaluation_competency.ts new file mode 100644 index 0000000..0b0eaf6 --- /dev/null +++ b/api/src/models/application_evaluation_competency.ts @@ -0,0 +1,100 @@ +import Sequelize, { DataTypes, Model, Optional } from 'sequelize'; +import type { ApplicationEvaluation, ApplicationEvaluationId } from './application_evaluation'; +import type { CompetencyEvaluation, CompetencyEvaluationId } from './competency_evaluation'; + +export interface ApplicationEvaluationCompetencyAttributes { + id: string; + application_evaluation_id?: string; + competency_evaluation_id?: string; + created_at?: Date; + updated_at?: Date; +} + +export type ApplicationEvaluationCompetencyPk = 'id'; +export type ApplicationEvaluationCompetencyId = ApplicationEvaluationCompetency[ApplicationEvaluationCompetencyPk]; +export type ApplicationEvaluationCompetencyCreationAttributes = Optional< + ApplicationEvaluationCompetencyAttributes, + ApplicationEvaluationCompetencyPk +>; + +export class ApplicationEvaluationCompetency + extends Model + implements ApplicationEvaluationCompetencyAttributes { + id!: string; + application_evaluation_id?: string; + competency_evaluation_id?: string; + created_at?: Date; + updated_at?: Date; + + // ApplicationEvaluationCompetency belongsTo ApplicationEvaluation + ApplicationEvaluation!: ApplicationEvaluation; + getApplicationEvaluation!: Sequelize.BelongsToGetAssociationMixin; + setApplicationEvaluation!: Sequelize.BelongsToSetAssociationMixin; + createApplicationEvaluation!: Sequelize.BelongsToCreateAssociationMixin; + // ApplicationEvaluationCompetency belongsTo CompetencyEvaluation + CompetencyEvaluation!: CompetencyEvaluation; + getCompetencyEvaluation!: Sequelize.BelongsToGetAssociationMixin; + setCompetencyEvaluation!: Sequelize.BelongsToSetAssociationMixin; + createCompetencyEvaluation!: Sequelize.BelongsToCreateAssociationMixin; + + static initModel(sequelize: Sequelize.Sequelize): typeof ApplicationEvaluationCompetency { + ApplicationEvaluationCompetency.init( + { + id: { + type: DataTypes.UUID, + allowNull: false, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + application_evaluation_id: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'application_evaluation', + key: 'id', + }, + unique: 'unique_combo_eval', + }, + competency_evaluation_id: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'competency_evaluation', + key: 'id', + }, + unique: 'unique_combo_eval', + }, + created_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.fn('now'), + }, + updated_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.fn('now'), + }, + }, + { + sequelize, + tableName: 'application_evaluation_competency', + schema: 'public', + hasTrigger: true, + timestamps: false, + indexes: [ + { + name: 'application_evaluation_competency_pkey', + unique: true, + fields: [{ name: 'id' }], + }, + { + name: 'unique_combo_eval', + unique: true, + fields: [{ name: 'application_evaluation_id' }, { name: 'competency_evaluation_id' }], + }, + ], + }, + ); + return ApplicationEvaluationCompetency; + } +} diff --git a/api/src/models/application_evaluation_points.ts b/api/src/models/application_evaluation_points.ts new file mode 100644 index 0000000..e8f9245 --- /dev/null +++ b/api/src/models/application_evaluation_points.ts @@ -0,0 +1,65 @@ +import Sequelize, { DataTypes, Model, Optional } from 'sequelize'; +import { ApplicationEvaluation } from './application_evaluation'; + +export interface ApplicationEvaluationPointsAttributes { + total_points?: number; + screened_out?: boolean; + competency_review_status?: string; + application_evaluation_id?: string; + evaluator?: string; + assessment_hurdle_id?: string; +} + +export type ApplicationEvaluationPointsCreationAttributes = ApplicationEvaluationPointsAttributes; + +export class ApplicationEvaluationPoints + extends Model + implements ApplicationEvaluationPointsAttributes { + total_points?: number; + screened_out?: boolean; + competency_review_status?: string; + application_evaluation_id?: string; + evaluator?: string; + assessment_hurdle_id?: string; + + ApplicationEvaluation!: ApplicationEvaluation; + getApplicationEvaluation!: Sequelize.BelongsToGetAssociationMixin; + + static initModel(sequelize: Sequelize.Sequelize): typeof ApplicationEvaluationPoints { + ApplicationEvaluationPoints.init( + { + total_points: { + type: DataTypes.BIGINT, + allowNull: true, + }, + screened_out: { + type: DataTypes.BOOLEAN, + allowNull: true, + }, + competency_review_status: { + type: DataTypes.TEXT, + allowNull: true, + }, + application_evaluation_id: { + type: DataTypes.UUID, + allowNull: true, + }, + evaluator: { + type: DataTypes.UUID, + allowNull: true, + }, + assessment_hurdle_id: { + type: DataTypes.UUID, + allowNull: true, + }, + }, + { + sequelize, + tableName: 'application_evaluation_points', + schema: 'public', + timestamps: false, + }, + ); + return ApplicationEvaluationPoints; + } +} diff --git a/api/src/models/application_evaluation_result.ts b/api/src/models/application_evaluation_result.ts new file mode 100644 index 0000000..4093524 --- /dev/null +++ b/api/src/models/application_evaluation_result.ts @@ -0,0 +1,75 @@ +import Sequelize, { DataTypes, Model, Optional } from 'sequelize'; + +export interface ApplicationEvaluationResultAttributes { + application_id?: string; + application_evaluation_id?: string; + assessment_hurdle_id?: string; + evaluation_note?: string; + evaluator?: string; + evaluation_approved?: boolean; + evaluation_result?: string; + evaluation_review_status?: string; +} + +export type ApplicationEvaluationResultCreationAttributes = ApplicationEvaluationResultAttributes; + +export class ApplicationEvaluationResult + extends Model + implements ApplicationEvaluationResultAttributes { + application_id?: string; + application_evaluation_id?: string; + screened_out?: boolean; + competency_review_status?: string; + evaluation_note?: string; + evaluator?: string; + evaluation_approved?: boolean; + evaluation_result?: string; + evaluation_review_status?: string; + assessment_hurdle_id?: string; + + static initModel(sequelize: Sequelize.Sequelize): typeof ApplicationEvaluationResult { + ApplicationEvaluationResult.init( + { + application_id: { + type: DataTypes.UUID, + allowNull: true, + }, + application_evaluation_id: { + type: DataTypes.UUID, + allowNull: true, + }, + evaluation_note: { + type: DataTypes.STRING(1500), + allowNull: true, + }, + evaluator: { + type: DataTypes.UUID, + allowNull: true, + }, + assessment_hurdle_id: { + type: DataTypes.UUID, + allowNull: true, + }, + evaluation_approved: { + type: DataTypes.BOOLEAN, + allowNull: true, + }, + evaluation_result: { + type: DataTypes.TEXT, + allowNull: true, + }, + evaluation_review_status: { + type: DataTypes.TEXT, + allowNull: true, + }, + }, + { + sequelize, + tableName: 'application_evaluation_result', + schema: 'public', + timestamps: false, + }, + ); + return ApplicationEvaluationResult; + } +} diff --git a/api/src/models/application_meta.ts b/api/src/models/application_meta.ts new file mode 100644 index 0000000..8284c6b --- /dev/null +++ b/api/src/models/application_meta.ts @@ -0,0 +1,96 @@ +import Sequelize, { DataTypes, Model, Optional } from 'sequelize'; +import type { Application, ApplicationId } from './application'; + +export interface ApplicationMetaAttributes { + id: string; + application_id?: string; + staffing_application_rating_id: string; + staffing_assessment_id: string; + staffing_rating_combination: string; + created_at?: Date; + updated_at?: Date; +} + +export type ApplicationMetaPk = 'id'; +export type ApplicationMetaId = ApplicationMeta[ApplicationMetaPk]; +export type ApplicationMetaCreationAttributes = Optional; + +export class ApplicationMeta extends Model implements ApplicationMetaAttributes { + id!: string; + application_id?: string; + staffing_application_rating_id!: string; + staffing_assessment_id!: string; + staffing_rating_combination!: string; + created_at?: Date; + updated_at?: Date; + + // ApplicationMeta belongsTo Application + Application!: Application; + getApplication!: Sequelize.BelongsToGetAssociationMixin; + setApplication!: Sequelize.BelongsToSetAssociationMixin; + createApplication!: Sequelize.BelongsToCreateAssociationMixin; + + static initModel(sequelize: Sequelize.Sequelize): typeof ApplicationMeta { + ApplicationMeta.init( + { + id: { + type: DataTypes.UUID, + allowNull: false, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + application_id: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'application', + key: 'id', + }, + unique: 'unique_application_meta', + }, + staffing_application_rating_id: { + type: DataTypes.STRING, + allowNull: false, + }, + staffing_assessment_id: { + type: DataTypes.STRING, + allowNull: false, + }, + staffing_rating_combination: { + type: DataTypes.STRING, + allowNull: false, + }, + created_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.fn('now'), + }, + updated_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.fn('now'), + }, + }, + { + sequelize, + tableName: 'application_meta', + schema: 'public', + hasTrigger: true, + timestamps: false, + indexes: [ + { + name: 'application_meta_pkey', + unique: true, + fields: [{ name: 'id' }], + }, + { + name: 'unique_application_meta', + unique: true, + fields: [{ name: 'application_id' }], + }, + ], + }, + ); + return ApplicationMeta; + } +} diff --git a/api/src/models/application_status_agg.ts b/api/src/models/application_status_agg.ts new file mode 100644 index 0000000..3810e09 --- /dev/null +++ b/api/src/models/application_status_agg.ts @@ -0,0 +1,63 @@ +import Sequelize, { DataTypes, Model } from 'sequelize'; + +export interface ApplicationStatusAggAttributes { + applicant_id?: string; + application_id?: string; + assessment_hurdle_id?: string; + evaluations_required?: number; + application_result?: string; + status?: string; +} + +export type ApplicationStatusAggCreationAttributes = ApplicationStatusAggAttributes; + +export class ApplicationStatusAgg + extends Model + implements ApplicationStatusAggAttributes { + applicant_id?: string; + application_id?: string; + assessment_hurdle_id?: string; + evaluations_required?: number; + application_result?: string; + status?: string; + + static initModel(sequelize: Sequelize.Sequelize): typeof ApplicationStatusAgg { + ApplicationStatusAgg.init( + { + application_id: { + type: DataTypes.UUID, + allowNull: true, + primaryKey: true, + }, + applicant_id: { + type: DataTypes.UUID, + allowNull: true, + }, + evaluations_required: { + type: DataTypes.INTEGER, + allowNull: true, + }, + assessment_hurdle_id: { + type: DataTypes.UUID, + allowNull: true, + }, + application_result: { + type: DataTypes.STRING, + allowNull: true, + }, + status: { + type: DataTypes.STRING, + allowNull: true, + }, + }, + { + sequelize, + tableName: 'application_status_agg', + schema: 'public', + timestamps: false, + freezeTableName: true, + }, + ); + return ApplicationStatusAgg; + } +} diff --git a/api/src/models/assessment_hurdle.ts b/api/src/models/assessment_hurdle.ts new file mode 100644 index 0000000..8441d6d --- /dev/null +++ b/api/src/models/assessment_hurdle.ts @@ -0,0 +1,193 @@ +import Sequelize, { DataTypes, Model, Optional } from 'sequelize'; +import type { Applicant, ApplicantId } from './applicant'; +import type { AssessmentHurdleMeta, AssessmentHurdleMetaCreationAttributes, AssessmentHurdleMetaId } from './assessment_hurdle_meta'; +import type { AssessmentHurdleUser, AssessmentHurdleUserId } from './assessment_hurdle_user'; +import type { Competency, CompetencyId } from './competency'; +import type { Specialty, SpecialtyId } from './specialty'; + +export interface AssessmentHurdleAttributes { + id: string; + department_name: string; + component_name?: string; + position_name: string; + assessment_name: string; + position_details?: string; + require_review_for_all_passing?: boolean; + locations?: string; + start_datetime: Date; + end_datetime: Date; + hurdle_display_type: number; + evaluations_required: number; + hr_name?: string; + hr_email?: string; + created_at?: Date; + updated_at?: Date; +} + +export type AssessmentHurdlePk = 'id'; +export type AssessmentHurdleId = AssessmentHurdle[AssessmentHurdlePk]; +export type AssessmentHurdleCreationAttributes = Optional; + +export class AssessmentHurdle extends Model implements AssessmentHurdleAttributes { + id!: string; + department_name!: string; + component_name?: string; + position_name!: string; + assessment_name!: string; + position_details?: string; + require_review_for_all_passing?: boolean; + locations?: string; + start_datetime!: Date; + end_datetime!: Date; + hurdle_display_type!: number; + evaluations_required!: number; + hr_name?: string; + hr_email?: string; + created_at?: Date; + updated_at?: Date; + + // AssessmentHurdle hasMany Applicant + Applicants!: Applicant[]; + getApplicants!: Sequelize.HasManyGetAssociationsMixin; + setApplicants!: Sequelize.HasManySetAssociationsMixin; + addApplicant!: Sequelize.HasManyAddAssociationMixin; + addApplicants!: Sequelize.HasManyAddAssociationsMixin; + createApplicant!: Sequelize.HasManyCreateAssociationMixin; + removeApplicant!: Sequelize.HasManyRemoveAssociationMixin; + removeApplicants!: Sequelize.HasManyRemoveAssociationsMixin; + hasApplicant!: Sequelize.HasManyHasAssociationMixin; + hasApplicants!: Sequelize.HasManyHasAssociationsMixin; + countApplicants!: Sequelize.HasManyCountAssociationsMixin; + // AssessmentHurdle hasOne AssessmentHurdleMeta + AssessmentHurdleMetum!: AssessmentHurdleMeta; + getAssessmentHurdleMetum!: Sequelize.HasOneGetAssociationMixin; + setAssessmentHurdleMetum!: Sequelize.HasOneSetAssociationMixin; + createAssessmentHurdleMetum!: Sequelize.HasOneCreateAssociationMixin; + // AssessmentHurdle hasMany AssessmentHurdleUser + AssessmentHurdleUsers!: AssessmentHurdleUser[]; + getAssessmentHurdleUsers!: Sequelize.HasManyGetAssociationsMixin; + setAssessmentHurdleUsers!: Sequelize.HasManySetAssociationsMixin; + addAssessmentHurdleUser!: Sequelize.HasManyAddAssociationMixin; + addAssessmentHurdleUsers!: Sequelize.HasManyAddAssociationsMixin; + createAssessmentHurdleUser!: Sequelize.HasManyCreateAssociationMixin; + removeAssessmentHurdleUser!: Sequelize.HasManyRemoveAssociationMixin; + removeAssessmentHurdleUsers!: Sequelize.HasManyRemoveAssociationsMixin; + hasAssessmentHurdleUser!: Sequelize.HasManyHasAssociationMixin; + hasAssessmentHurdleUsers!: Sequelize.HasManyHasAssociationsMixin; + countAssessmentHurdleUsers!: Sequelize.HasManyCountAssociationsMixin; + // AssessmentHurdle hasMany Competency + Competencies!: Competency[]; + getCompetencies!: Sequelize.HasManyGetAssociationsMixin; + setCompetencies!: Sequelize.HasManySetAssociationsMixin; + addCompetency!: Sequelize.HasManyAddAssociationMixin; + addCompetencies!: Sequelize.HasManyAddAssociationsMixin; + createCompetency!: Sequelize.HasManyCreateAssociationMixin; + removeCompetency!: Sequelize.HasManyRemoveAssociationMixin; + removeCompetencies!: Sequelize.HasManyRemoveAssociationsMixin; + hasCompetency!: Sequelize.HasManyHasAssociationMixin; + hasCompetencies!: Sequelize.HasManyHasAssociationsMixin; + countCompetencies!: Sequelize.HasManyCountAssociationsMixin; + // AssessmentHurdle hasMany Specialty + Specialties!: Specialty[]; + getSpecialties!: Sequelize.HasManyGetAssociationsMixin; + setSpecialties!: Sequelize.HasManySetAssociationsMixin; + addSpecialty!: Sequelize.HasManyAddAssociationMixin; + addSpecialties!: Sequelize.HasManyAddAssociationsMixin; + createSpecialty!: Sequelize.HasManyCreateAssociationMixin; + removeSpecialty!: Sequelize.HasManyRemoveAssociationMixin; + removeSpecialties!: Sequelize.HasManyRemoveAssociationsMixin; + hasSpecialty!: Sequelize.HasManyHasAssociationMixin; + hasSpecialties!: Sequelize.HasManyHasAssociationsMixin; + countSpecialties!: Sequelize.HasManyCountAssociationsMixin; + + static initModel(sequelize: Sequelize.Sequelize): typeof AssessmentHurdle { + AssessmentHurdle.init( + { + id: { + type: DataTypes.UUID, + allowNull: false, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + department_name: { + type: DataTypes.STRING, + allowNull: false, + }, + component_name: { + type: DataTypes.STRING, + allowNull: true, + }, + position_name: { + type: DataTypes.STRING, + allowNull: false, + }, + assessment_name: { + type: DataTypes.STRING, + allowNull: false, + }, + position_details: { + type: DataTypes.STRING, + allowNull: true, + }, + locations: { + type: DataTypes.STRING, + allowNull: true, + }, + start_datetime: { + type: DataTypes.DATE, + allowNull: false, + }, + end_datetime: { + type: DataTypes.DATE, + allowNull: false, + }, + hurdle_display_type: { + type: DataTypes.INTEGER, + allowNull: false, + }, + require_review_for_all_passing: { + type: DataTypes.BOOLEAN, + allowNull: false, + }, + evaluations_required: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 1, + }, + hr_name: { + type: DataTypes.STRING, + allowNull: true, + }, + hr_email: { + type: DataTypes.STRING, + allowNull: true, + }, + created_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.fn('now'), + }, + updated_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.fn('now'), + }, + }, + { + sequelize, + tableName: 'assessment_hurdle', + schema: 'public', + hasTrigger: true, + timestamps: false, + indexes: [ + { + name: 'assessment_hurdle_pkey', + unique: true, + fields: [{ name: 'id' }], + }, + ], + }, + ); + return AssessmentHurdle; + } +} diff --git a/api/src/models/assessment_hurdle_meta.ts b/api/src/models/assessment_hurdle_meta.ts new file mode 100644 index 0000000..466ee2a --- /dev/null +++ b/api/src/models/assessment_hurdle_meta.ts @@ -0,0 +1,116 @@ +import Sequelize, { DataTypes, Model, Optional } from 'sequelize'; +import type { AssessmentHurdle, AssessmentHurdleId } from './assessment_hurdle'; + +export interface AssessmentHurdleMetaAttributes { + id: string; + staffing_vacancy_id: string; + staffing_assessment_id: string; + staffing_pass_nor?: string; + staffing_fail_nor?: string; + assessment_hurdle_id?: string; + created_at?: Date; + updated_at?: Date; +} + +export type AssessmentHurdleMetaPk = 'id'; +export type AssessmentHurdleMetaId = AssessmentHurdleMeta[AssessmentHurdleMetaPk]; +export type AssessmentHurdleMetaCreationAttributes = Optional; + +export class AssessmentHurdleMeta + extends Model + implements AssessmentHurdleMetaAttributes { + id!: string; + staffing_vacancy_id!: string; + staffing_assessment_id!: string; + staffing_pass_nor?: string; + staffing_fail_nor?: string; + assessment_hurdle_id?: string; + created_at?: Date; + updated_at?: Date; + + // AssessmentHurdleMeta belongsTo AssessmentHurdle + AssessmentHurdle!: AssessmentHurdle; + getAssessmentHurdle!: Sequelize.BelongsToGetAssociationMixin; + setAssessmentHurdle!: Sequelize.BelongsToSetAssociationMixin; + createAssessmentHurdle!: Sequelize.BelongsToCreateAssociationMixin; + + static initModel(sequelize: Sequelize.Sequelize): typeof AssessmentHurdleMeta { + AssessmentHurdleMeta.init( + { + id: { + type: DataTypes.UUID, + allowNull: false, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + staffing_vacancy_id: { + type: DataTypes.STRING, + allowNull: false, + unique: 'unique_hurdle', + }, + staffing_assessment_id: { + type: DataTypes.STRING, + allowNull: false, + unique: 'unique_hurdle', + }, + staffing_pass_nor: { + type: DataTypes.STRING, + allowNull: true, + }, + staffing_fail_nor: { + type: DataTypes.STRING, + allowNull: true, + }, + assessment_hurdle_id: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'assessment_hurdle', + key: 'id', + }, + unique: 'unique_meta_hurdle', + }, + created_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.fn('now'), + }, + updated_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.fn('now'), + }, + }, + { + sequelize, + tableName: 'assessment_hurdle_meta', + schema: 'public', + hasTrigger: true, + timestamps: false, + indexes: [ + { + name: 'assessment_hurdle_meta_pkey', + unique: true, + fields: [{ name: 'id' }], + }, + { + name: 'assessment_hurdle_meta_staffing_vacancy_id_key', + unique: true, + fields: [{ name: 'staffing_vacancy_id' }], + }, + { + name: 'unique_hurdle', + unique: true, + fields: [{ name: 'staffing_vacancy_id' }, { name: 'staffing_assessment_id' }], + }, + { + name: 'unique_meta_hurdle', + unique: true, + fields: [{ name: 'assessment_hurdle_id' }], + }, + ], + }, + ); + return AssessmentHurdleMeta; + } +} diff --git a/api/src/models/assessment_hurdle_user.ts b/api/src/models/assessment_hurdle_user.ts new file mode 100644 index 0000000..7e7554f --- /dev/null +++ b/api/src/models/assessment_hurdle_user.ts @@ -0,0 +1,104 @@ +import Sequelize, { DataTypes, Model, Optional } from 'sequelize'; +import type { AppUser, AppUserId } from './app_user'; +import type { AssessmentHurdle, AssessmentHurdleId } from './assessment_hurdle'; + +export interface AssessmentHurdleUserAttributes { + id: string; + app_user_id: string; + role: number; + assessment_hurdle_id: string; + created_at?: Date; + updated_at?: Date; +} + +export type AssessmentHurdleUserPk = 'id'; +export type AssessmentHurdleUserId = AssessmentHurdleUser[AssessmentHurdleUserPk]; +export type AssessmentHurdleUserCreationAttributes = Optional; + +export class AssessmentHurdleUser + extends Model + implements AssessmentHurdleUserAttributes { + id!: string; + app_user_id!: string; + role!: number; + assessment_hurdle_id!: string; + created_at?: Date; + updated_at?: Date; + + // AssessmentHurdleUser belongsTo AppUser + AppUser!: AppUser; + getAppUser!: Sequelize.BelongsToGetAssociationMixin; + setAppUser!: Sequelize.BelongsToSetAssociationMixin; + createAppUser!: Sequelize.BelongsToCreateAssociationMixin; + // AssessmentHurdleUser belongsTo AssessmentHurdle + AssessmentHurdle!: AssessmentHurdle; + getAssessmentHurdle!: Sequelize.BelongsToGetAssociationMixin; + setAssessmentHurdle!: Sequelize.BelongsToSetAssociationMixin; + createAssessmentHurdle!: Sequelize.BelongsToCreateAssociationMixin; + + static initModel(sequelize: Sequelize.Sequelize): typeof AssessmentHurdleUser { + AssessmentHurdleUser.init( + { + id: { + type: DataTypes.UUID, + allowNull: false, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + app_user_id: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'app_user', + key: 'id', + }, + unique: 'user_in_assessment_hurdle', + }, + role: { + type: DataTypes.INTEGER, + allowNull: false, + unique: 'user_in_assessment_hurdle', + }, + assessment_hurdle_id: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'assessment_hurdle', + key: 'id', + }, + unique: 'user_in_assessment_hurdle', + }, + created_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.fn('now'), + }, + updated_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.fn('now'), + }, + }, + { + sequelize, + tableName: 'assessment_hurdle_user', + schema: 'public', + hasTrigger: true, + timestamps: false, + indexes: [ + { + name: 'assessment_hurdle_user_pkey', + unique: true, + fields: [{ name: 'id' }], + }, + { + name: 'user_in_assessment_hurdle', + unique: true, + fields: [{ name: 'assessment_hurdle_id' }, { name: 'app_user_id' }, { name: 'role' }], + }, + ], + }, + ); + return AssessmentHurdleUser; + } +} diff --git a/api/src/models/competency.ts b/api/src/models/competency.ts new file mode 100644 index 0000000..0ecaf27 --- /dev/null +++ b/api/src/models/competency.ts @@ -0,0 +1,168 @@ +import Sequelize, { DataTypes, Model, Optional } from 'sequelize'; +import type { AssessmentHurdle, AssessmentHurdleId } from './assessment_hurdle'; +import type { CompetencyEvaluation, CompetencyEvaluationId } from './competency_evaluation'; +import type { CompetencySelectors, CompetencySelectorsId } from './competency_selectors'; +import type { SpecialtyCompetencies, SpecialtyCompetenciesId } from './specialty_competencies'; + +export interface CompetencyAttributes { + id: string; + name: string; + local_id: string; + assessment_hurdle_id?: string; + definition: string; + required_proficiency_definition?: string; + display_type?: number; + screen_out?: boolean; + sort_order?: number; + created_at?: Date; + updated_at?: Date; +} + +export type CompetencyPk = 'id'; +export type CompetencyId = Competency[CompetencyPk]; +export type CompetencyCreationAttributes = Optional; + +export class Competency extends Model implements CompetencyAttributes { + id!: string; + name!: string; + local_id!: string; + assessment_hurdle_id?: string; + definition!: string; + required_proficiency_definition?: string; + display_type?: number; + screen_out?: boolean; + sort_order?: number; + created_at?: Date; + updated_at?: Date; + + // Competency belongsTo AssessmentHurdle + AssessmentHurdle!: AssessmentHurdle; + getAssessmentHurdle!: Sequelize.BelongsToGetAssociationMixin; + setAssessmentHurdle!: Sequelize.BelongsToSetAssociationMixin; + createAssessmentHurdle!: Sequelize.BelongsToCreateAssociationMixin; + // Competency hasMany CompetencyEvaluation + CompetencyEvaluations!: CompetencyEvaluation[]; + getCompetencyEvaluations!: Sequelize.HasManyGetAssociationsMixin; + setCompetencyEvaluations!: Sequelize.HasManySetAssociationsMixin; + addCompetencyEvaluation!: Sequelize.HasManyAddAssociationMixin; + addCompetencyEvaluations!: Sequelize.HasManyAddAssociationsMixin; + createCompetencyEvaluation!: Sequelize.HasManyCreateAssociationMixin; + removeCompetencyEvaluation!: Sequelize.HasManyRemoveAssociationMixin; + removeCompetencyEvaluations!: Sequelize.HasManyRemoveAssociationsMixin; + hasCompetencyEvaluation!: Sequelize.HasManyHasAssociationMixin; + hasCompetencyEvaluations!: Sequelize.HasManyHasAssociationsMixin; + countCompetencyEvaluations!: Sequelize.HasManyCountAssociationsMixin; + // Competency hasMany CompetencySelectors + CompetencySelectors!: CompetencySelectors[]; + getCompetencySelectors!: Sequelize.HasManyGetAssociationsMixin; + setCompetencySelectors!: Sequelize.HasManySetAssociationsMixin; + addCompetencySelector!: Sequelize.HasManyAddAssociationMixin; + addCompetencySelectors!: Sequelize.HasManyAddAssociationsMixin; + createCompetencySelector!: Sequelize.HasManyCreateAssociationMixin; + removeCompetencySelector!: Sequelize.HasManyRemoveAssociationMixin; + removeCompetencySelectors!: Sequelize.HasManyRemoveAssociationsMixin; + hasCompetencySelector!: Sequelize.HasManyHasAssociationMixin; + hasCompetencySelectors!: Sequelize.HasManyHasAssociationsMixin; + countCompetencySelectors!: Sequelize.HasManyCountAssociationsMixin; + // Competency hasMany SpecialtyCompetencies + SpecialtyCompetencies!: SpecialtyCompetencies[]; + getSpecialtyCompetencies!: Sequelize.HasManyGetAssociationsMixin; + setSpecialtyCompetencies!: Sequelize.HasManySetAssociationsMixin; + addSpecialtyCompetency!: Sequelize.HasManyAddAssociationMixin; + addSpecialtyCompetencies!: Sequelize.HasManyAddAssociationsMixin; + createSpecialtyCompetency!: Sequelize.HasManyCreateAssociationMixin; + removeSpecialtyCompetency!: Sequelize.HasManyRemoveAssociationMixin; + removeSpecialtyCompetencies!: Sequelize.HasManyRemoveAssociationsMixin; + hasSpecialtyCompetency!: Sequelize.HasManyHasAssociationMixin; + hasSpecialtyCompetencies!: Sequelize.HasManyHasAssociationsMixin; + countSpecialtyCompetencies!: Sequelize.HasManyCountAssociationsMixin; + + static initModel(sequelize: Sequelize.Sequelize): typeof Competency { + Competency.init( + { + id: { + type: DataTypes.UUID, + allowNull: false, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + name: { + type: DataTypes.STRING, + allowNull: false, + unique: 'unique_name', + }, + local_id: { + type: DataTypes.STRING, + allowNull: false, + unique: 'unique_local_id', + }, + assessment_hurdle_id: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'assessment_hurdle', + key: 'id', + }, + unique: 'unique_name', + }, + definition: { + type: DataTypes.STRING(1500), + allowNull: false, + }, + required_proficiency_definition: { + type: DataTypes.STRING(1500), + allowNull: true, + }, + display_type: { + type: DataTypes.INTEGER, + allowNull: true, + defaultValue: 0, + }, + screen_out: { + type: DataTypes.BOOLEAN, + allowNull: true, + }, + sort_order: { + type: DataTypes.INTEGER, + defaultValue: 0, + allowNull: false, + }, + created_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.fn('now'), + }, + updated_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.fn('now'), + }, + }, + { + sequelize, + tableName: 'competency', + schema: 'public', + hasTrigger: true, + timestamps: false, + indexes: [ + { + name: 'competency_pkey', + unique: true, + fields: [{ name: 'id' }], + }, + { + name: 'unique_local_id', + unique: true, + fields: [{ name: 'assessment_hurdle_id' }, { name: 'local_id' }], + }, + { + name: 'unique_name', + unique: true, + fields: [{ name: 'name' }, { name: 'assessment_hurdle_id' }], + }, + ], + }, + ); + return Competency; + } +} diff --git a/api/src/models/competency_evaluation.ts b/api/src/models/competency_evaluation.ts new file mode 100644 index 0000000..0af7254 --- /dev/null +++ b/api/src/models/competency_evaluation.ts @@ -0,0 +1,145 @@ +import Sequelize, { DataTypes, Model, Optional } from 'sequelize'; +import type { AppUser, AppUserId } from './app_user'; +import type { ApplicationEvaluationCompetency, ApplicationEvaluationCompetencyId } from './application_evaluation_competency'; +import type { Competency, CompetencyId } from './competency'; +import type { CompetencySelectors, CompetencySelectorsId } from './competency_selectors'; +import { Applicant, ApplicantId } from './applicant'; + +export interface CompetencyEvaluationAttributes { + id: string; + evaluation_note?: string; + evaluator: string; + applicant: string; + competency_id: string; + competency_selector_id: string; + created_at?: Date; + updated_at?: Date; +} + +export type CompetencyEvaluationPk = 'id'; +export type CompetencyEvaluationId = CompetencyEvaluation[CompetencyEvaluationPk]; +export type CompetencyEvaluationCreationAttributes = Optional; + +export class CompetencyEvaluation + extends Model + implements CompetencyEvaluationAttributes { + id!: string; + evaluation_note?: string; + evaluator!: string; + applicant!: string; + competency_id!: string; + competency_selector_id!: string; + created_at?: Date; + updated_at?: Date; + + // CompetencyEvaluation hasMany ApplicationEvaluationCompetency + ApplicationEvaluationCompetencies!: ApplicationEvaluationCompetency[]; + getApplicationEvaluationCompetencies!: Sequelize.HasManyGetAssociationsMixin; + setApplicationEvaluationCompetencies!: Sequelize.HasManySetAssociationsMixin; + addApplicationEvaluationCompetency!: Sequelize.HasManyAddAssociationMixin; + addApplicationEvaluationCompetencies!: Sequelize.HasManyAddAssociationsMixin; + createApplicationEvaluationCompetency!: Sequelize.HasManyCreateAssociationMixin; + removeApplicationEvaluationCompetency!: Sequelize.HasManyRemoveAssociationMixin; + removeApplicationEvaluationCompetencies!: Sequelize.HasManyRemoveAssociationsMixin< + ApplicationEvaluationCompetency, + ApplicationEvaluationCompetencyId + >; + hasApplicationEvaluationCompetency!: Sequelize.HasManyHasAssociationMixin; + hasApplicationEvaluationCompetencies!: Sequelize.HasManyHasAssociationsMixin; + countApplicationEvaluationCompetencies!: Sequelize.HasManyCountAssociationsMixin; + // CompetencyEvaluation belongsTo AppUser + AppUser!: AppUser; + getAppUser!: Sequelize.BelongsToGetAssociationMixin; + setAppUser!: Sequelize.BelongsToSetAssociationMixin; + createAppUser!: Sequelize.BelongsToCreateAssociationMixin; + // CompetencyEvaluation belongsTo Competency + Competency!: Competency; + getCompetency!: Sequelize.BelongsToGetAssociationMixin; + setCompetency!: Sequelize.BelongsToSetAssociationMixin; + createCompetency!: Sequelize.BelongsToCreateAssociationMixin; + // CompetencyEvaluation belongsTo CompetencySelectors + CompetencySelector!: CompetencySelectors; + getCompetencySelector!: Sequelize.BelongsToGetAssociationMixin; + setCompetencySelector!: Sequelize.BelongsToSetAssociationMixin; + createCompetencySelector!: Sequelize.BelongsToCreateAssociationMixin; + // CompetencyEvaluation belongsTo Applicant + Applicant!: Applicant; + getApplicant!: Sequelize.BelongsToGetAssociationMixin; + setApplicant!: Sequelize.BelongsToSetAssociationMixin; + createApplicant!: Sequelize.BelongsToCreateAssociationMixin; + + static initModel(sequelize: Sequelize.Sequelize): typeof CompetencyEvaluation { + CompetencyEvaluation.init( + { + id: { + type: DataTypes.UUID, + allowNull: false, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + evaluation_note: { + type: DataTypes.STRING(1500), + allowNull: true, + }, + evaluator: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'app_user', + key: 'id', + }, + }, + applicant: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'applicant', + key: 'id', + }, + }, + competency_id: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'competency', + key: 'id', + }, + }, + competency_selector_id: { + type: DataTypes.UUID, + allowNull: false, + references: { + model: 'competency_selectors', + key: 'id', + }, + }, + created_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.fn('now'), + }, + updated_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.fn('now'), + }, + }, + { + sequelize, + tableName: 'competency_evaluation', + schema: 'public', + hasTrigger: true, + timestamps: false, + indexes: [ + { name: 'unique_competency_evaluation', unique: true, fields: ['applicant', 'evaluator', 'competency_id'] }, + { + name: 'competency_evaluation_pkey', + unique: true, + fields: [{ name: 'id' }], + }, + ], + }, + ); + return CompetencyEvaluation; + } +} diff --git a/api/src/models/competency_evaluation_count.ts b/api/src/models/competency_evaluation_count.ts new file mode 100644 index 0000000..7f1cfc8 --- /dev/null +++ b/api/src/models/competency_evaluation_count.ts @@ -0,0 +1,45 @@ +import Sequelize, { DataTypes, Model } from 'sequelize'; +import { CompetencyEvaluation } from './competency_evaluation'; + +export interface CompetencyEvaluationCountAttributes { + assessment_hurdle_id?: string; + competency_id?: string; + applicant?: string; + does_not_meet?: number; + meets?: number; + screen_out?: boolean; +} + +export type CompetencyEvaluationCountCreationAttributes = CompetencyEvaluationCountAttributes; + +export class CompetencyEvaluationCount + extends Model + implements CompetencyEvaluationCountAttributes { + assessment_hurdle_id?: string; + competency_id?: string; + applicant?: string; + does_not_meet?: number; + meets?: number; + screen_out?: boolean; + CompetencyEvaluation!: CompetencyEvaluation; + + static initModel(sequelize: Sequelize.Sequelize): typeof CompetencyEvaluationCount { + CompetencyEvaluationCount.init( + { + assessment_hurdle_id: { type: DataTypes.UUID, allowNull: true }, + competency_id: { type: DataTypes.UUID, allowNull: true }, + applicant: { type: DataTypes.UUID, allowNull: true }, + does_not_meet: { type: DataTypes.NUMBER, allowNull: true }, + meets: { type: DataTypes.NUMBER, allowNull: true }, + screen_out: { type: DataTypes.BOOLEAN, allowNull: true }, + }, + { + sequelize, + tableName: 'competency_evaluation_count', + schema: 'public', + timestamps: false, + }, + ); + return CompetencyEvaluationCount; + } +} diff --git a/api/src/models/competency_selectors.ts b/api/src/models/competency_selectors.ts new file mode 100644 index 0000000..304d14d --- /dev/null +++ b/api/src/models/competency_selectors.ts @@ -0,0 +1,120 @@ +import Sequelize, { DataTypes, Model, Optional } from 'sequelize'; +import type { Competency, CompetencyId } from './competency'; +import type { CompetencyEvaluation, CompetencyEvaluationId } from './competency_evaluation'; + +export interface CompetencySelectorsAttributes { + id: string; + sort_order?: number; + display_name?: string; + point_value?: number; + default_text?: string; + competency_id?: string; + created_at?: Date; + updated_at?: Date; +} + +export type CompetencySelectorsPk = 'id'; +export type CompetencySelectorsId = CompetencySelectors[CompetencySelectorsPk]; +export type CompetencySelectorsCreationAttributes = Optional; + +export class CompetencySelectors + extends Model + implements CompetencySelectorsAttributes { + id!: string; + sort_order?: number; + display_name?: string; + point_value?: number; + default_text?: string; + competency_id?: string; + created_at?: Date; + updated_at?: Date; + + // CompetencySelectors hasMany CompetencyEvaluation + CompetencyEvaluations!: CompetencyEvaluation[]; + getCompetencyEvaluations!: Sequelize.HasManyGetAssociationsMixin; + setCompetencyEvaluations!: Sequelize.HasManySetAssociationsMixin; + addCompetencyEvaluation!: Sequelize.HasManyAddAssociationMixin; + addCompetencyEvaluations!: Sequelize.HasManyAddAssociationsMixin; + createCompetencyEvaluation!: Sequelize.HasManyCreateAssociationMixin; + removeCompetencyEvaluation!: Sequelize.HasManyRemoveAssociationMixin; + removeCompetencyEvaluations!: Sequelize.HasManyRemoveAssociationsMixin; + hasCompetencyEvaluation!: Sequelize.HasManyHasAssociationMixin; + hasCompetencyEvaluations!: Sequelize.HasManyHasAssociationsMixin; + countCompetencyEvaluations!: Sequelize.HasManyCountAssociationsMixin; + // CompetencySelectors belongsTo Competency + Competency!: Competency; + getCompetency!: Sequelize.BelongsToGetAssociationMixin; + setCompetency!: Sequelize.BelongsToSetAssociationMixin; + createCompetency!: Sequelize.BelongsToCreateAssociationMixin; + + static initModel(sequelize: Sequelize.Sequelize): typeof CompetencySelectors { + CompetencySelectors.init( + { + id: { + type: DataTypes.UUID, + allowNull: false, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + sort_order: { + type: DataTypes.INTEGER, + allowNull: true, + defaultValue: 0, + }, + display_name: { + type: DataTypes.STRING, + allowNull: true, + unique: 'unique_competency', + }, + point_value: { + type: DataTypes.INTEGER, + allowNull: true, + defaultValue: 0, + }, + default_text: { + type: DataTypes.STRING, + allowNull: true, + }, + competency_id: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'competency', + key: 'id', + }, + unique: 'unique_competency', + }, + created_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.fn('now'), + }, + updated_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.fn('now'), + }, + }, + { + sequelize, + tableName: 'competency_selectors', + schema: 'public', + hasTrigger: true, + timestamps: false, + indexes: [ + { + name: 'competency_selectors_pkey', + unique: true, + fields: [{ name: 'id' }], + }, + { + name: 'unique_competency', + unique: true, + fields: [{ name: 'display_name' }, { name: 'competency_id' }], + }, + ], + }, + ); + return CompetencySelectors; + } +} diff --git a/api/src/models/evaluator_metrics.ts b/api/src/models/evaluator_metrics.ts new file mode 100644 index 0000000..1a3f5ca --- /dev/null +++ b/api/src/models/evaluator_metrics.ts @@ -0,0 +1,67 @@ +import Sequelize, { DataTypes, Model } from 'sequelize'; + +export interface EvaluatorMetricsAttributes { + evaluator?: string; + assessment_hurdle_id?: string; + pending_review?: number; + pending_amendment?: number; + completed?: number; + recusals?: number; + name?: string; +} + +export type EvaluatorMetricsCreationAttributes = EvaluatorMetricsAttributes; + +export class EvaluatorMetrics extends Model implements EvaluatorMetricsAttributes { + evaluator?: string; + name?: string; + assessment_hurdle_id?: string; + pending_review?: number; + pending_amendment?: number; + completed?: number; + recusals?: number; + + static initModel(sequelize: Sequelize.Sequelize): typeof EvaluatorMetrics { + EvaluatorMetrics.init( + { + evaluator: { + type: DataTypes.UUID, + allowNull: true, + primaryKey: true, + }, + assessment_hurdle_id: { + type: DataTypes.UUID, + allowNull: true, + }, + pending_review: { + type: DataTypes.INTEGER, + allowNull: true, + }, + pending_amendment: { + type: DataTypes.INTEGER, + allowNull: true, + }, + completed: { + type: DataTypes.INTEGER, + allowNull: true, + }, + recusals: { + type: DataTypes.INTEGER, + allowNull: true, + }, + name: { + type: DataTypes.STRING, + allowNull: true, + }, + }, + { + sequelize, + tableName: 'evaluator_metrics', + schema: 'public', + timestamps: false, + freezeTableName: true, + }, + ); + return EvaluatorMetrics; + } +} diff --git a/api/src/models/init-models.ts b/api/src/models/init-models.ts new file mode 100644 index 0000000..c9c2d47 --- /dev/null +++ b/api/src/models/init-models.ts @@ -0,0 +1,294 @@ +import type { Sequelize, Model } from 'sequelize'; +import { AppUser } from './app_user'; +import type { AppUserAttributes, AppUserCreationAttributes } from './app_user'; +import { Applicant } from './applicant'; +import type { ApplicantAttributes, ApplicantCreationAttributes } from './applicant'; +import { ApplicantMeta } from './applicant_meta'; +import type { ApplicantMetaAttributes, ApplicantMetaCreationAttributes } from './applicant_meta'; +import { ApplicantRecusals } from './applicant_recusals'; +import type { ApplicantRecusalsAttributes, ApplicantRecusalsCreationAttributes } from './applicant_recusals'; +import { ApplicantStatusMetrics } from './applicant_status_metrics'; +import type { ApplicantStatusMetricsAttributes, ApplicantStatusMetricsCreationAttributes } from './applicant_status_metrics'; +import { Application } from './application'; +import type { ApplicationAttributes, ApplicationCreationAttributes } from './application'; +import { ApplicationAssignments } from './application_assignments'; +import type { ApplicationAssignmentsAttributes, ApplicationAssignmentsCreationAttributes } from './application_assignments'; +import { ApplicationEvaluation } from './application_evaluation'; +import type { ApplicationEvaluationAttributes, ApplicationEvaluationCreationAttributes } from './application_evaluation'; +import { ApplicationEvaluationCompetency } from './application_evaluation_competency'; +import type { + ApplicationEvaluationCompetencyAttributes, + ApplicationEvaluationCompetencyCreationAttributes, +} from './application_evaluation_competency'; +import { ApplicantEvaluationFeedback } from './applicant_evaluation_feedback'; +import type { ApplicantEvaluationFeedbackAttributes, ApplicantEvaluationFeedbackCreationAttributes } from './applicant_evaluation_feedback'; +import { ApplicationEvaluationPoints } from './application_evaluation_points'; +import type { ApplicationEvaluationPointsAttributes, ApplicationEvaluationPointsCreationAttributes } from './application_evaluation_points'; +import { CompetencyEvaluationCount } from './competency_evaluation_count'; +import type { CompetencyEvaluationCountAttributes, CompetencyEvaluationCountCreationAttributes } from './competency_evaluation_count'; +import { ApplicantApplicationEvaluationNotes } from './applicant_application_evaluation_notes'; +import { + ApplicantApplicationEvaluationNotesAttributes, + ApplicantApplicationEvaluationNotesCreationAttributes, +} from './applicant_application_evaluation_notes'; +import { ApplicantQueue } from './applicant_queue'; +import type { ApplicantQueueAttributes, ApplicantQueueCreationAttributes } from './applicant_queue'; +import { ApplicationEvaluationResult } from './application_evaluation_result'; +import type { ApplicationEvaluationResultAttributes, ApplicationEvaluationResultCreationAttributes } from './application_evaluation_result'; +import { ApplicationMeta } from './application_meta'; +import type { ApplicationMetaAttributes, ApplicationMetaCreationAttributes } from './application_meta'; +import { EvaluatorMetrics } from './evaluator_metrics'; +import { ReviewerMetrics } from './reviewer_metrics'; +import { ApplicationEvaluationAgg } from './application_evaluation_agg'; +import type { ApplicationEvaluationAggAttributes, ApplicationEvaluationAggCreationAttributes } from './application_evaluation_agg'; +import type { EvaluatorMetricsAttributes, EvaluatorMetricsCreationAttributes } from './evaluator_metrics'; +import type { ReviewerMetricsAttributes, ReviewerMetricsCreationAttributes } from './reviewer_metrics'; +import { ApplicationStatusAgg } from './application_status_agg'; +import type { ApplicationStatusAggAttributes, ApplicationStatusAggCreationAttributes } from './application_status_agg'; +import { AssessmentHurdle } from './assessment_hurdle'; +import type { AssessmentHurdleAttributes, AssessmentHurdleCreationAttributes } from './assessment_hurdle'; +import { AssessmentHurdleMeta } from './assessment_hurdle_meta'; +import type { AssessmentHurdleMetaAttributes, AssessmentHurdleMetaCreationAttributes } from './assessment_hurdle_meta'; +import { AssessmentHurdleUser } from './assessment_hurdle_user'; +import type { AssessmentHurdleUserAttributes, AssessmentHurdleUserCreationAttributes } from './assessment_hurdle_user'; +import { Competency } from './competency'; +import type { CompetencyAttributes, CompetencyCreationAttributes } from './competency'; +import { CompetencyEvaluation } from './competency_evaluation'; +import type { CompetencyEvaluationAttributes, CompetencyEvaluationCreationAttributes } from './competency_evaluation'; +import { CompetencySelectors } from './competency_selectors'; +import type { CompetencySelectorsAttributes, CompetencySelectorsCreationAttributes } from './competency_selectors'; +import { Specialty } from './specialty'; +import type { SpecialtyAttributes, SpecialtyCreationAttributes } from './specialty'; +import { SpecialtyCompetencies } from './specialty_competencies'; +import type { SpecialtyCompetenciesAttributes, SpecialtyCompetenciesCreationAttributes } from './specialty_competencies'; + +export { + AppUser, + Applicant, + ApplicantMeta, + ApplicantRecusals, + ApplicantStatusMetrics, + Application, + ApplicationAssignments, + ApplicationEvaluation, + ApplicationEvaluationCompetency, + ApplicantEvaluationFeedback, + ApplicationEvaluationPoints, + CompetencyEvaluationCount, + ApplicantApplicationEvaluationNotes, + ApplicantQueue, + ApplicationEvaluationResult, + ApplicationMeta, + ApplicationEvaluationAgg, + EvaluatorMetrics, + ReviewerMetrics, + ApplicationStatusAgg, + AssessmentHurdle, + AssessmentHurdleMeta, + AssessmentHurdleUser, + Competency, + CompetencyEvaluation, + CompetencySelectors, + Specialty, + SpecialtyCompetencies, +}; + +export type { + AppUserAttributes, + AppUserCreationAttributes, + ApplicantAttributes, + ApplicantCreationAttributes, + ApplicantMetaAttributes, + ApplicantMetaCreationAttributes, + ApplicantRecusalsAttributes, + ApplicantRecusalsCreationAttributes, + ApplicantStatusMetricsAttributes, + ApplicantStatusMetricsCreationAttributes, + ApplicationAttributes, + ApplicationCreationAttributes, + ApplicationAssignmentsAttributes, + ApplicationAssignmentsCreationAttributes, + ApplicationEvaluationAttributes, + ApplicationEvaluationCreationAttributes, + ApplicationEvaluationCompetencyAttributes, + ApplicationEvaluationCompetencyCreationAttributes, + ApplicantEvaluationFeedbackAttributes, + ApplicantEvaluationFeedbackCreationAttributes, + ApplicationEvaluationPointsAttributes, + ApplicationEvaluationPointsCreationAttributes, + CompetencyEvaluationCountAttributes, + CompetencyEvaluationCountCreationAttributes, + ApplicantApplicationEvaluationNotesAttributes, + ApplicantApplicationEvaluationNotesCreationAttributes, + ApplicantQueueAttributes, + ApplicantQueueCreationAttributes, + ApplicationEvaluationResultAttributes, + ApplicationEvaluationResultCreationAttributes, + ApplicationMetaAttributes, + ApplicationMetaCreationAttributes, + ApplicationEvaluationAggAttributes, + ApplicationEvaluationAggCreationAttributes, + EvaluatorMetricsAttributes, + EvaluatorMetricsCreationAttributes, + ReviewerMetricsAttributes, + ReviewerMetricsCreationAttributes, + ApplicationStatusAggAttributes, + ApplicationStatusAggCreationAttributes, + AssessmentHurdleAttributes, + AssessmentHurdleCreationAttributes, + AssessmentHurdleMetaAttributes, + AssessmentHurdleMetaCreationAttributes, + AssessmentHurdleUserAttributes, + AssessmentHurdleUserCreationAttributes, + CompetencyAttributes, + CompetencyCreationAttributes, + CompetencyEvaluationAttributes, + CompetencyEvaluationCreationAttributes, + CompetencySelectorsAttributes, + CompetencySelectorsCreationAttributes, + SpecialtyAttributes, + SpecialtyCreationAttributes, + SpecialtyCompetenciesAttributes, + SpecialtyCompetenciesCreationAttributes, +}; + +export function initModels(sequelize: Sequelize) { + AppUser.initModel(sequelize); + Applicant.initModel(sequelize); + ApplicantMeta.initModel(sequelize); + ApplicantRecusals.initModel(sequelize); + ApplicantStatusMetrics.initModel(sequelize); + Application.initModel(sequelize); + ApplicationAssignments.initModel(sequelize); + ApplicationEvaluation.initModel(sequelize); + ApplicationEvaluationCompetency.initModel(sequelize); + ApplicantEvaluationFeedback.initModel(sequelize); + ApplicationEvaluationPoints.initModel(sequelize); + CompetencyEvaluationCount.initModel(sequelize); + ApplicantApplicationEvaluationNotes.initModel(sequelize); + ApplicantQueue.initModel(sequelize); + ApplicationEvaluationResult.initModel(sequelize); + ApplicationMeta.initModel(sequelize); + ApplicationEvaluationAgg.initModel(sequelize); + EvaluatorMetrics.initModel(sequelize); + ReviewerMetrics.initModel(sequelize); + ApplicationStatusAgg.initModel(sequelize); + AssessmentHurdle.initModel(sequelize); + AssessmentHurdleMeta.initModel(sequelize); + AssessmentHurdleUser.initModel(sequelize); + Competency.initModel(sequelize); + CompetencyEvaluation.initModel(sequelize); + CompetencySelectors.initModel(sequelize); + Specialty.initModel(sequelize); + SpecialtyCompetencies.initModel(sequelize); + + Applicant.belongsTo(AssessmentHurdle, { foreignKey: 'assessment_hurdle_id' }); + AssessmentHurdle.hasMany(Applicant, { foreignKey: 'assessment_hurdle_id' }); + ApplicantMeta.belongsTo(Applicant, { foreignKey: 'applicant_id' }); + Applicant.hasOne(ApplicantMeta, { foreignKey: 'applicant_id' }); + ApplicantRecusals.belongsTo(Applicant, { foreignKey: 'applicant_id' }); + Applicant.hasMany(ApplicantRecusals, { foreignKey: 'applicant_id' }); + ApplicantRecusals.belongsTo(AppUser, { foreignKey: 'recused_evaluator_id' }); + AppUser.hasMany(ApplicantRecusals, { foreignKey: 'recused_evaluator_id' }); + Application.belongsTo(Applicant, { foreignKey: 'applicant_id' }); + Applicant.hasMany(Application, { foreignKey: 'applicant_id' }); + Application.belongsTo(Specialty, { foreignKey: 'specialty_id' }); + Specialty.hasMany(Application, { foreignKey: 'specialty_id' }); + ApplicationAssignments.belongsTo(Applicant, { foreignKey: 'applicant_id' }); + Applicant.hasMany(ApplicationAssignments, { foreignKey: 'applicant_id' }); + ApplicationAssignments.belongsTo(AppUser, { foreignKey: 'evaluator_id' }); + AppUser.hasMany(ApplicationAssignments, { foreignKey: 'evaluator_id' }); + ApplicationEvaluation.belongsTo(Application, { foreignKey: 'application_id' }); + Application.hasMany(ApplicationEvaluation, { foreignKey: 'application_id' }); + ApplicationEvaluation.belongsTo(AppUser, { foreignKey: 'approver_id', as: 'Approver' }); + AppUser.hasMany(ApplicationEvaluation, { foreignKey: 'approver_id' }); + ApplicationEvaluation.belongsTo(AppUser, { foreignKey: 'evaluator', as: 'Evaluator' }); + AppUser.hasMany(ApplicationEvaluation, { foreignKey: 'evaluator' }); + ApplicationEvaluationCompetency.belongsTo(ApplicationEvaluation, { foreignKey: 'application_evaluation_id' }); + ApplicationEvaluation.hasMany(ApplicationEvaluationCompetency, { foreignKey: 'application_evaluation_id' }); + ApplicationEvaluationCompetency.belongsTo(CompetencyEvaluation, { foreignKey: 'competency_evaluation_id' }); + CompetencyEvaluation.hasMany(ApplicationEvaluationCompetency, { foreignKey: 'competency_evaluation_id' }); + + ApplicantEvaluationFeedback.belongsTo(AppUser, { foreignKey: 'applicant_id' }); + AppUser.hasMany(ApplicantEvaluationFeedback, { foreignKey: 'applicant_id' }); + + CompetencyEvaluation.belongsTo(Applicant, { foreignKey: 'applicant' }); + Applicant.hasMany(CompetencyEvaluation, { foreignKey: 'applicant' }); + + ApplicantEvaluationFeedback.belongsTo(AppUser, { foreignKey: 'evaluator_id' }); + AppUser.hasMany(ApplicantEvaluationFeedback, { foreignKey: 'evaluator_id' }); + ApplicantEvaluationFeedback.belongsTo(AppUser, { foreignKey: 'feedback_author_id' }); + AppUser.hasMany(ApplicantEvaluationFeedback, { foreignKey: 'feedback_author_id' }); + ApplicationMeta.belongsTo(Application, { foreignKey: 'application_id' }); + Application.hasOne(ApplicationMeta, { foreignKey: 'application_id' }); + AssessmentHurdleMeta.belongsTo(AssessmentHurdle, { foreignKey: 'assessment_hurdle_id' }); + AssessmentHurdle.hasOne(AssessmentHurdleMeta, { foreignKey: 'assessment_hurdle_id' }); + AssessmentHurdleUser.belongsTo(AppUser, { foreignKey: 'app_user_id' }); + AppUser.hasMany(AssessmentHurdleUser, { foreignKey: 'app_user_id' }); + AssessmentHurdleUser.belongsTo(AssessmentHurdle, { foreignKey: 'assessment_hurdle_id' }); + AssessmentHurdle.hasMany(AssessmentHurdleUser, { foreignKey: 'assessment_hurdle_id' }); + Competency.belongsTo(AssessmentHurdle, { foreignKey: 'assessment_hurdle_id' }); + AssessmentHurdle.hasMany(Competency, { foreignKey: 'assessment_hurdle_id' }); + CompetencyEvaluation.belongsTo(Competency, { foreignKey: 'competency_id' }); + Competency.hasMany(CompetencyEvaluation, { foreignKey: 'competency_id' }); + CompetencyEvaluation.belongsTo(CompetencySelectors, { foreignKey: 'competency_selector_id' }); + CompetencySelectors.hasMany(CompetencyEvaluation, { foreignKey: 'competency_selector_id' }); + CompetencyEvaluation.belongsTo(AppUser, { foreignKey: 'evaluator' }); + AppUser.hasMany(CompetencyEvaluation, { foreignKey: 'evaluator' }); + CompetencySelectors.belongsTo(Competency, { foreignKey: 'competency_id' }); + Competency.hasMany(CompetencySelectors, { foreignKey: 'competency_id' }); + Specialty.belongsTo(AssessmentHurdle, { foreignKey: 'assessment_hurdle_id' }); + AssessmentHurdle.hasMany(Specialty, { foreignKey: 'assessment_hurdle_id' }); + SpecialtyCompetencies.belongsTo(Competency, { foreignKey: 'competency_id' }); + Competency.hasMany(SpecialtyCompetencies, { foreignKey: 'competency_id' }); + SpecialtyCompetencies.belongsTo(Specialty, { foreignKey: 'specialty_id' }); + Specialty.hasMany(SpecialtyCompetencies, { foreignKey: 'specialty_id' }); + + Applicant.belongsTo(ApplicantEvaluationFeedback, { foreignKey: 'id', targetKey: 'applicant_id' }); + ApplicantEvaluationFeedback.hasOne(Applicant, { foreignKey: 'id', sourceKey: 'applicant_id' }); + + ApplicationAssignments.belongsTo(AssessmentHurdle, { foreignKey: 'assessment_hurdle_id' }); + AssessmentHurdle.hasMany(ApplicationAssignments, { foreignKey: 'assessment_hurdle_id' }); + + Application.belongsTo(ApplicationAssignments, { foreignKey: 'id', targetKey: 'applicant_id' }); + ApplicationAssignments.hasOne(Application, { foreignKey: 'id', sourceKey: 'applicant_id' }); + + Applicant.belongsTo(ApplicantStatusMetrics, { foreignKey: 'id', targetKey: 'applicant_id' }); + ApplicantStatusMetrics.hasOne(Applicant, { foreignKey: 'id', sourceKey: 'applicant_id' }); + + ApplicationEvaluation.belongsTo(ApplicationEvaluationPoints, { foreignKey: 'id', targetKey: 'application_evaluation_id' }); + ApplicationEvaluationPoints.hasOne(ApplicationEvaluation, { foreignKey: 'id', sourceKey: 'application_evaluation_id' }); + + // CompetencyEvaluation.belongsTo(CompetencyEvaluationCount, { foreignKey: 'id', targetKey: 'competency_evaluation_id' }); + // CompetencyEvaluationCount.hasOne(CompetencyEvaluation, { foreignKey: 'id', sourceKey: 'competency_evaluation_id' }); + return { + AppUser: AppUser, + Applicant: Applicant, + ApplicantMeta: ApplicantMeta, + ApplicantRecusals: ApplicantRecusals, + ApplicantStatusMetrics, + Application: Application, + ApplicationAssignments: ApplicationAssignments, + ApplicationEvaluation: ApplicationEvaluation, + ApplicationEvaluationCompetency: ApplicationEvaluationCompetency, + ApplicantEvaluationFeedback: ApplicantEvaluationFeedback, + ApplicationEvaluationPoints: ApplicationEvaluationPoints, + CompetencyEvaluationCount: CompetencyEvaluationCount, + ApplicantApplicationEvaluationNotes: ApplicantApplicationEvaluationNotes, + ApplicantQueue: ApplicantQueue, + ApplicationEvaluationResult: ApplicationEvaluationResult, + ApplicationMeta: ApplicationMeta, + ApplicationEvaluationAgg, + EvaluatorMetrics, + ReviewerMetrics, + ApplicationStatusAgg, + AssessmentHurdle: AssessmentHurdle, + AssessmentHurdleMeta: AssessmentHurdleMeta, + AssessmentHurdleUser: AssessmentHurdleUser, + Competency: Competency, + CompetencyEvaluation: CompetencyEvaluation, + CompetencySelectors: CompetencySelectors, + Specialty: Specialty, + SpecialtyCompetencies: SpecialtyCompetencies, + }; +} diff --git a/api/src/models/reviewer_metrics.ts b/api/src/models/reviewer_metrics.ts new file mode 100644 index 0000000..05cb0ea --- /dev/null +++ b/api/src/models/reviewer_metrics.ts @@ -0,0 +1,61 @@ +import Sequelize, { DataTypes, Model } from 'sequelize'; + +export interface ReviewerMetricsAttributes { + reviewer_id?: string; + name?: string; + email?: string; + assessment_hurdle_id?: string; + pending_amendment?: number; + adjudicated?: number; +} + +export type ReviewerMetricsCreationAttributes = ReviewerMetricsAttributes; + +export class ReviewerMetrics extends Model implements ReviewerMetricsAttributes { + reviewer_id?: string; + name?: string; + email?: string; + assessment_hurdle_id?: string; + pending_amendment?: number; + adjudicated?: number; + + static initModel(sequelize: Sequelize.Sequelize): typeof ReviewerMetrics { + ReviewerMetrics.init( + { + reviewer_id: { + type: DataTypes.UUID, + allowNull: true, + primaryKey: true, + }, + assessment_hurdle_id: { + type: DataTypes.UUID, + allowNull: true, + }, + pending_amendment: { + type: DataTypes.INTEGER, + allowNull: true, + }, + adjudicated: { + type: DataTypes.INTEGER, + allowNull: true, + }, + name: { + type: DataTypes.STRING, + allowNull: true, + }, + email: { + type: DataTypes.STRING, + allowNull: true, + }, + }, + { + sequelize, + tableName: 'reviewer_metrics', + schema: 'public', + timestamps: false, + freezeTableName: true, + }, + ); + return ReviewerMetrics; + } +} diff --git a/api/src/models/specialty.ts b/api/src/models/specialty.ts new file mode 100644 index 0000000..aeaf140 --- /dev/null +++ b/api/src/models/specialty.ts @@ -0,0 +1,130 @@ +import Sequelize, { DataTypes, Model, Optional } from 'sequelize'; +import type { Application, ApplicationId } from './application'; +import type { AssessmentHurdle, AssessmentHurdleId } from './assessment_hurdle'; +import type { SpecialtyCompetencies, SpecialtyCompetenciesId } from './specialty_competencies'; + +export interface SpecialtyAttributes { + id: string; + name: string; + local_id: string; + assessment_hurdle_id?: string; + points_required?: number; + created_at?: Date; + updated_at?: Date; +} + +export type SpecialtyPk = 'id'; +export type SpecialtyId = Specialty[SpecialtyPk]; +export type SpecialtyCreationAttributes = Optional; + +export class Specialty extends Model implements SpecialtyAttributes { + id!: string; + name!: string; + local_id!: string; + assessment_hurdle_id?: string; + points_required?: number; + created_at?: Date; + updated_at?: Date; + + // Specialty hasMany Application + Applications!: Application[]; + getApplications!: Sequelize.HasManyGetAssociationsMixin; + setApplications!: Sequelize.HasManySetAssociationsMixin; + addApplication!: Sequelize.HasManyAddAssociationMixin; + addApplications!: Sequelize.HasManyAddAssociationsMixin; + createApplication!: Sequelize.HasManyCreateAssociationMixin; + removeApplication!: Sequelize.HasManyRemoveAssociationMixin; + removeApplications!: Sequelize.HasManyRemoveAssociationsMixin; + hasApplication!: Sequelize.HasManyHasAssociationMixin; + hasApplications!: Sequelize.HasManyHasAssociationsMixin; + countApplications!: Sequelize.HasManyCountAssociationsMixin; + // Specialty belongsTo AssessmentHurdle + AssessmentHurdle!: AssessmentHurdle; + getAssessmentHurdle!: Sequelize.BelongsToGetAssociationMixin; + setAssessmentHurdle!: Sequelize.BelongsToSetAssociationMixin; + createAssessmentHurdle!: Sequelize.BelongsToCreateAssociationMixin; + // Specialty hasMany SpecialtyCompetencies + SpecialtyCompetencies!: SpecialtyCompetencies[]; + getSpecialtyCompetencies!: Sequelize.HasManyGetAssociationsMixin; + setSpecialtyCompetencies!: Sequelize.HasManySetAssociationsMixin; + addSpecialtyCompetency!: Sequelize.HasManyAddAssociationMixin; + addSpecialtyCompetencies!: Sequelize.HasManyAddAssociationsMixin; + createSpecialtyCompetency!: Sequelize.HasManyCreateAssociationMixin; + removeSpecialtyCompetency!: Sequelize.HasManyRemoveAssociationMixin; + removeSpecialtyCompetencies!: Sequelize.HasManyRemoveAssociationsMixin; + hasSpecialtyCompetency!: Sequelize.HasManyHasAssociationMixin; + hasSpecialtyCompetencies!: Sequelize.HasManyHasAssociationsMixin; + countSpecialtyCompetencies!: Sequelize.HasManyCountAssociationsMixin; + + static initModel(sequelize: Sequelize.Sequelize): typeof Specialty { + Specialty.init( + { + id: { + type: DataTypes.UUID, + allowNull: false, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + name: { + type: DataTypes.STRING, + allowNull: false, + unique: 'unique_specialty', + }, + local_id: { + type: DataTypes.STRING, + allowNull: false, + unique: 'unique_specialty_mapping', + }, + assessment_hurdle_id: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'assessment_hurdle', + key: 'id', + }, + unique: 'unique_specialty_mapping', + }, + points_required: { + type: DataTypes.INTEGER, + allowNull: true, + defaultValue: 1, + }, + created_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.fn('now'), + }, + updated_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.fn('now'), + }, + }, + { + sequelize, + tableName: 'specialty', + schema: 'public', + hasTrigger: true, + timestamps: false, + indexes: [ + { + name: 'specialty_pkey', + unique: true, + fields: [{ name: 'id' }], + }, + { + name: 'unique_specialty', + unique: true, + fields: [{ name: 'name' }, { name: 'assessment_hurdle_id' }], + }, + { + name: 'unique_specialty_mapping', + unique: true, + fields: [{ name: 'local_id' }, { name: 'assessment_hurdle_id' }], + }, + ], + }, + ); + return Specialty; + } +} diff --git a/api/src/models/specialty_competencies.ts b/api/src/models/specialty_competencies.ts new file mode 100644 index 0000000..612ffa2 --- /dev/null +++ b/api/src/models/specialty_competencies.ts @@ -0,0 +1,97 @@ +import Sequelize, { DataTypes, Model, Optional } from 'sequelize'; +import type { Competency, CompetencyId } from './competency'; +import type { Specialty, SpecialtyId } from './specialty'; + +export interface SpecialtyCompetenciesAttributes { + id: string; + specialty_id?: string; + competency_id?: string; + created_at?: Date; + updated_at?: Date; +} + +export type SpecialtyCompetenciesPk = 'id'; +export type SpecialtyCompetenciesId = SpecialtyCompetencies[SpecialtyCompetenciesPk]; +export type SpecialtyCompetenciesCreationAttributes = Optional; + +export class SpecialtyCompetencies + extends Model + implements SpecialtyCompetenciesAttributes { + id!: string; + specialty_id?: string; + competency_id?: string; + created_at?: Date; + updated_at?: Date; + + // SpecialtyCompetencies belongsTo Competency + Competency!: Competency; + getCompetency!: Sequelize.BelongsToGetAssociationMixin; + setCompetency!: Sequelize.BelongsToSetAssociationMixin; + createCompetency!: Sequelize.BelongsToCreateAssociationMixin; + // SpecialtyCompetencies belongsTo Specialty + Specialty!: Specialty; + getSpecialty!: Sequelize.BelongsToGetAssociationMixin; + setSpecialty!: Sequelize.BelongsToSetAssociationMixin; + createSpecialty!: Sequelize.BelongsToCreateAssociationMixin; + + static initModel(sequelize: Sequelize.Sequelize): typeof SpecialtyCompetencies { + SpecialtyCompetencies.init( + { + id: { + type: DataTypes.UUID, + allowNull: false, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + specialty_id: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'specialty', + key: 'id', + }, + unique: 'unique_combo', + }, + competency_id: { + type: DataTypes.UUID, + allowNull: true, + references: { + model: 'competency', + key: 'id', + }, + unique: 'unique_combo', + }, + created_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.fn('now'), + }, + updated_at: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.fn('now'), + }, + }, + { + sequelize, + tableName: 'specialty_competencies', + schema: 'public', + hasTrigger: true, + timestamps: false, + indexes: [ + { + name: 'specialty_competencies_pkey', + unique: true, + fields: [{ name: 'id' }], + }, + { + name: 'unique_combo', + unique: true, + fields: [{ name: 'specialty_id' }, { name: 'competency_id' }], + }, + ], + }, + ); + return SpecialtyCompetencies; + } +} diff --git a/api/src/routes/admin.routes.ts b/api/src/routes/admin.routes.ts new file mode 100644 index 0000000..bd48442 --- /dev/null +++ b/api/src/routes/admin.routes.ts @@ -0,0 +1,26 @@ +import AdminHandler from '../handlers/admin.handler'; +import ExportHandler from '../handlers/export.handler'; +import AuthenticationMiddleware from '../middlewares/auth.middleware'; +import { BaseRouter } from './route'; + +export default class AdminRoute extends BaseRouter { + handler = new AdminHandler(); + exportHandler = new ExportHandler(); + authMiddleware = new AuthenticationMiddleware(); + + constructor() { + super(); + this.path = '/admin'; + this.initRoutes(); + } + initRoutes() { + this.router.get(`${this.path}/health`, this.handler.healthCheck); + this.router.get(`${this.path}/export/:assessmentHurdleId/audit`, this.authMiddleware.authorizedAdminToken, this.exportHandler.getAuditFile); + this.router.get(`${this.path}/export/:assessmentHurdleId/results`, this.authMiddleware.authorizedAdminToken, this.exportHandler.getResultsFile); + + this.router.get(`${this.path}/export/:assessmentHurdleId/resultsusas`, this.authMiddleware.authorizedAdminToken, this.exportHandler.getUSASFile); + + // this.router.get(`${this.path}/export/:assessmentHurdleId/recusals`, this.authMiddleware.authorizedAdminToken, this.exportHandler.getRecusedFile); + // this.router.get(`${this.path}/export/:assessmentHurdleId/flagged`, this.authMiddleware.authorizedAdminToken, this.exportHandler.getFlaggedFile); + } +} diff --git a/api/src/routes/application.routes.ts b/api/src/routes/application.routes.ts new file mode 100644 index 0000000..391f5c7 --- /dev/null +++ b/api/src/routes/application.routes.ts @@ -0,0 +1,32 @@ +import CreateApplicationDto from '../dto/createapplication.dto'; +import CreateApplicationSpecialtyMappingDto from '../dto/createapplicationmapping.dto'; +import ApplicationHandler from '../handlers/application.handler'; +import AuthenticationMiddleware from '../middlewares/auth.middleware'; +import validationMiddleware from '../middlewares/validator.middleware'; +import { BaseRouter } from './route'; + +export default class ApplicationRouter extends BaseRouter { + handler = new ApplicationHandler(); + authMiddleware = new AuthenticationMiddleware(); + + constructor() { + super(); + this.path = '/application'; + this.initRoutes(); + } + + initRoutes() { + // It's unclear if any of these are used aside from the creation scripts + // Locking them behind an admin token. + this.router.use(`${this.path}`, this.authMiddleware.authorizedAdminToken); + + this.router.get(`${this.path}/:applicationId`, this.handler.getById); + this.router.get(`${this.path}/:applicationId/meta`, this.handler.getByIdWithMeta); + this.router.get(`${this.path}/applicant/:applicantId`, this.handler.getAllByApplicantId); + this.router.get(`${this.path}/specialty/:specialtyId`, this.handler.getAllBySpecialtyId); + + this.router.put(`${this.path}/`, validationMiddleware(CreateApplicationDto, 'body'), this.handler.upsert); + this.router.put(`${this.path}/mapping`, validationMiddleware(CreateApplicationSpecialtyMappingDto, 'body'), this.handler.createMapping); + this.router.delete(`${this.path}/mapping`, validationMiddleware(CreateApplicationSpecialtyMappingDto, 'body'), this.handler.deleteMapping); + } +} diff --git a/api/src/routes/assessmenthurdle.routes.ts b/api/src/routes/assessmenthurdle.routes.ts new file mode 100644 index 0000000..c2564d3 --- /dev/null +++ b/api/src/routes/assessmenthurdle.routes.ts @@ -0,0 +1,84 @@ +import path from 'path'; +import multer from 'multer'; +import { CreateApplicantBulkDto } from '../dto/createapplicant.dto'; +import CreateAssessmentHurdleDto from '../dto/createassessmenthurdle.dto'; +import CreateCompetenciesDto from '../dto/createcompetencies.dto'; +import CreateCompetencyDto from '../dto/createcompetency.dto'; +import CreateHurdleUserDto from '../dto/createhurdleuser.dto'; +import CreateSpecialtiesDto from '../dto/createspecialties.dto'; +import CreateSpecialtyDto from '../dto/createspecialty.dto'; +import ApplicantHandler from '../handlers/applicant.handler'; +import ApplicationAssignmentHandler from '../handlers/applicationassignment.handler'; +import AssessmentHurdleHandler from '../handlers/assessmenthurdle.handler'; +import CompetencyHandler from '../handlers/competency.handler'; +import SpecialtyHandler from '../handlers/specialty.handler'; +import UsersHandler from '../handlers/users.handler'; +import validationMiddleware from '../middlewares/validator.middleware'; + +import { BaseRouter } from './route'; +import AuthenticationMiddleware from '../middlewares/auth.middleware'; +import ExportHandler from '../handlers/export.handler'; + +const upload = multer({ dest: path.join(__dirname, '..', 'tmp') }); +export default class AssessmentHurdleRoute extends BaseRouter { + handler = new AssessmentHurdleHandler(); + compHandler = new CompetencyHandler(); + specHandler = new SpecialtyHandler(); + applicantHandler = new ApplicantHandler(); + userHandler = new UsersHandler(); + assignmentHandler = new ApplicationAssignmentHandler(); + authMiddleware = new AuthenticationMiddleware(); + exportHandler = new ExportHandler(); + + constructor() { + super(); + this.path = '/assessment-hurdle'; + this.initRoutes(); + } + private initRoutes() { + /** Authorized Users (all) */ + this.router.get(`${this.path}/roles`, this.authMiddleware.authenticatedUser, this.handler.getAllForUser); + /** Authorized Users (all) */ + + this.router.get(`${this.path}/:assessmentHurdleId`, this.authMiddleware.authorizedUser, this.handler.getById); + + /** Authorized Evaluator routes */ + this.router.get(`${this.path}/:assessmentHurdleId/next`, this.authMiddleware.authorizedEvaluator, this.assignmentHandler.nextAssignment); + this.router.get(`${this.path}/:assessmentHurdleId/current`, this.authMiddleware.authorizedEvaluator, this.assignmentHandler.getByEvaluatorId); + + /** Authorized Reviewer routes */ + this.router.get(`${this.path}/:assessmentHurdleId/hrdisplay`, this.authMiddleware.authorizedReviewer, this.handler.getHrDisplay); + this.router.get(`${this.path}/:assessmentHurdleId/metrics`, this.authMiddleware.authorizedReviewer, this.exportHandler.getResultsFile); + + this.router.get(`${this.path}/export/:assessmentHurdleId/resultsusas`, this.authMiddleware.authorizedReviewer, this.exportHandler.getUSASFile); + + this.router.get(`${this.path}/export/:assessmentHurdleId/audit`, this.authMiddleware.authorizedReviewer, this.exportHandler.getAuditFile); + /** ADMIN PATHS */ + this.router.use(`${this.path}`, this.authMiddleware.authorizedAdminToken); + + this.router.get(`${this.path}`, this.handler.getAll); + this.router.put(`${this.path}`, validationMiddleware(CreateAssessmentHurdleDto, 'body'), this.handler.upsert); + + this.router.put(`${this.path}/:assessmentHurdleId/specialty`, validationMiddleware(CreateSpecialtyDto, 'body'), this.specHandler.upsert); + this.router.put(`${this.path}/:assessmentHurdleId/specialties`, validationMiddleware(CreateSpecialtiesDto, 'body'), this.specHandler.upsertAll); + this.router.put(`${this.path}/:assessmentHurdleId/competency`, validationMiddleware(CreateCompetencyDto, 'body'), this.compHandler.upsert); + this.router.put(`${this.path}/:assessmentHurdleId/competencies`, validationMiddleware(CreateCompetenciesDto, 'body'), this.compHandler.upsertAll); + this.router.get(`${this.path}/:assessmentHurdleId/specialty/:specialtyId`, this.specHandler.getAllMappingsById); + this.router.get(`${this.path}/:assessmentHurdleId/competency/:competencyId`, this.compHandler.getAllMappingsById); + + this.router.put( + `${this.path}/:assessmentHurdleId/applicant`, + validationMiddleware(CreateApplicantBulkDto, 'body'), + this.applicantHandler.upsertBulk, + ); + + this.router.post(`${this.path}/:assessmentHurdleId/importUSAS`, upload.any(), this.applicantHandler.importUSAS); + this.router.get(`${this.path}/:assessmentHurdleId/applicant/:applicantId`, this.applicantHandler.getByIdWithMeta); + + this.router.put( + `${this.path}/:assessmentHurdleId/users`, + validationMiddleware(CreateHurdleUserDto, 'body'), + this.userHandler.createUserAndAddToHurdle, + ); + } +} diff --git a/api/src/routes/authentication.routes.ts b/api/src/routes/authentication.routes.ts new file mode 100644 index 0000000..6bfb7bb --- /dev/null +++ b/api/src/routes/authentication.routes.ts @@ -0,0 +1,25 @@ +import { Router } from 'express'; +import AuthenticationHandler from '../handlers/authentication.handler'; +import { BaseRouter } from './route'; + +/** + * All routes after this will require a user to be logged in + */ +export default class AuthenticationRouter extends BaseRouter { + router = Router(); + handler = new AuthenticationHandler(); + + constructor() { + super(); + this.basePath = '/'; + this.path = '/'; + this.initRoutes(); + } + + private initRoutes() { + this.router.get(`/login/auth`, this.handler.loginUser); + this.router.get(`/logout`, this.handler.logoutUser); + this.router.get(`/login/check`, this.handler.checkUser); + this.router.post(`/login/token`, this.handler.tokenLogin); + } +} diff --git a/api/src/routes/evaluation.routes.ts b/api/src/routes/evaluation.routes.ts new file mode 100644 index 0000000..c0c814c --- /dev/null +++ b/api/src/routes/evaluation.routes.ts @@ -0,0 +1,55 @@ +import { Router } from 'express'; +import ApplicantFlagDto from '../dto/applicantflag.dto'; +import { EvaluationApplicationFeedbackDto } from '../dto/evaluationfeedback.dto'; +import { EvaluationApplicationReviewSubmitDto } from '../dto/evaluationreviewsubmit.dto'; +import EvaluationSubmitDto from '../dto/evaluationsubmit.dto'; +import ApplicantHandler from '../handlers/applicant.handler'; +import EvaluationHandler from '../handlers/evaluation.handler'; +import AuthenticationMiddleware from '../middlewares/auth.middleware'; +import validationMiddleware from '../middlewares/validator.middleware'; +import { BaseRouter } from './route'; + +export default class EvaluationRouter extends BaseRouter { + router = Router(); + applicantHandler = new ApplicantHandler(); + evaluationHandler = new EvaluationHandler(); + authMiddleware = new AuthenticationMiddleware(); + + constructor() { + super(); + this.path = '/evaluation/:assessmentHurdleId'; + this.initRoutes(); + } + + private initRoutes() { + this.router.put( + `${this.path}/application/review`, + this.authMiddleware.authorizedReviewer, + validationMiddleware(EvaluationApplicationReviewSubmitDto, 'body'), + this.evaluationHandler.submitApplicationReview, + ); + + this.router.put( + `${this.path}/feedback/:applicantId`, + this.authMiddleware.authorizedReviewer, + validationMiddleware(EvaluationApplicationFeedbackDto, 'body'), + this.evaluationHandler.submitApplicationFeedback, + ); + this.router.post( + `${this.path}/flag/:applicantId`, + this.authMiddleware.authorizedUser, + validationMiddleware(ApplicantFlagDto, 'body'), + this.applicantHandler.flag, + ); + + /** + * Evaluator only routes + */ + this.router.use(`${this.path}`, this.authMiddleware.authorizedEvaluator); + this.router.put(`${this.path}/recuse/:applicantId`, this.applicantHandler.recuse); + this.router.get(`${this.path}/display/:applicantId`, this.applicantHandler.getDisplay); + this.router.put(`${this.path}/submit/:applicantId`, validationMiddleware(EvaluationSubmitDto, 'body'), this.evaluationHandler.submitEvaluation); + + // this.router.get(`${this.path}/:hurdleId/stats/:applicantId`); + } +} diff --git a/api/src/routes/metrics.routes.ts b/api/src/routes/metrics.routes.ts new file mode 100644 index 0000000..04ee37a --- /dev/null +++ b/api/src/routes/metrics.routes.ts @@ -0,0 +1,21 @@ +import { Router } from 'express'; +import MetricsHandler from '../handlers/metrics.handler'; +import AuthenticationMiddleware from '../middlewares/auth.middleware'; +import { BaseRouter } from './route'; + +export default class MetricsRouter extends BaseRouter { + router = Router(); + metricsHandler = new MetricsHandler(); + authMiddleware = new AuthenticationMiddleware(); + + constructor() { + super(); + this.path = '/metrics/assessment_hurdle/:assessmentHurdleId'; + this.initRoutes(); + } + + private initRoutes() { + this.router.get(`${this.path}/getMetrics`, this.authMiddleware.authorizedReviewer, this.metricsHandler.getOverallStatus); + this.router.get(`${this.path}/evaluator/`, this.authMiddleware.authorizedEvaluator, this.metricsHandler.getEvaluatorProgress); + } +} diff --git a/api/src/routes/review.routes.ts b/api/src/routes/review.routes.ts new file mode 100644 index 0000000..e9a34e2 --- /dev/null +++ b/api/src/routes/review.routes.ts @@ -0,0 +1,41 @@ +import { Router } from 'express'; +import { ApplicantFeedbackSubmitDto } from '../dto/applicantFeedbackSubmit.dto'; +import ApplicantHandler from '../handlers/applicant.handler'; +import EvaluationHandler from '../handlers/evaluation.handler'; +import ReviewHandler from '../handlers/review.handler'; +import AuthenticationMiddleware from '../middlewares/auth.middleware'; +import validationMiddleware from '../middlewares/validator.middleware'; +import { BaseRouter } from './route'; + +export default class ReviewRouter extends BaseRouter { + router = Router(); + applicantHandler = new ApplicantHandler(); + evaluationHandler = new EvaluationHandler(); + authMiddleware = new AuthenticationMiddleware(); + reviewHandler = new ReviewHandler(); + + constructor() { + super(); + this.path = '/review/:assessmentHurdleId'; + this.initRoutes(); + } + + private initRoutes() { + /** + * Reviewer only routes + */ + this.router.use(`${this.path}`, this.authMiddleware.authorizedReviewer); + + this.router.put( + `${this.path}/applicant/:applicantId/applicant_feedback`, + validationMiddleware(ApplicantFeedbackSubmitDto), + this.reviewHandler.submitApplicantFeedback, + ); + + this.router.post( + `${this.path}/applicant/:applicantId/release`, + validationMiddleware(ApplicantFeedbackSubmitDto), + this.reviewHandler.releaseApplicant, + ); + } +} diff --git a/api/src/routes/route.ts b/api/src/routes/route.ts new file mode 100644 index 0000000..3dd888c --- /dev/null +++ b/api/src/routes/route.ts @@ -0,0 +1,25 @@ +import { Router } from 'express'; +import Route from '../interfaces/routes.interface'; + +export abstract class BaseRouter implements Route { + router = Router(); + + private _path = '/'; + private _basePath = '/api'; + + set path(path: string) { + this._path = path; + } + get path() { + return this._path; + } + set basePath(path: string) { + this._basePath = path; + } + get basePath() { + return this._basePath || '/'; + } + get fullBasePath() { + return `${this._basePath}${this._path}`; + } +} diff --git a/api/src/routes/users.routes.ts b/api/src/routes/users.routes.ts new file mode 100644 index 0000000..a046add --- /dev/null +++ b/api/src/routes/users.routes.ts @@ -0,0 +1,26 @@ +import { Router } from 'express'; +import CreateUserDto from '../dto/createuser.dto'; +import UsersHandler from '../handlers/users.handler'; +import validationMiddleware from '../middlewares/validator.middleware'; +import AuthenticationMiddleware from '../middlewares/auth.middleware'; +import { BaseRouter } from './route'; +export default class UsersRoute extends BaseRouter { + router = Router(); + handler = new UsersHandler(); + authMiddleware = new AuthenticationMiddleware(); + + constructor() { + super(); + this.path = '/users'; + this.initRoutes(); + } + + private initRoutes() { + this.router.use(`${this.path}`, this.authMiddleware.authorizedAdminToken); + + this.router.get(`${this.path}`, this.handler.getUsers); + this.router.put(`${this.path}`, validationMiddleware(CreateUserDto, 'body'), this.handler.createUser); + this.router.get(`${this.path}/:userId`, this.handler.getUserById); + this.router.get(`${this.path}/email/:userEmail`, this.handler.getUserByEmail); + } +} diff --git a/api/src/server.ts b/api/src/server.ts new file mode 100644 index 0000000..f534861 --- /dev/null +++ b/api/src/server.ts @@ -0,0 +1,37 @@ +import * as dotenv from 'dotenv'; +import App from './app'; +import Routes from './interfaces/routes.interface'; +import AdminRoute from './routes/admin.routes'; +import ApplicationRoute from './routes/application.routes'; +import AssessmentHurdleRoute from './routes/assessmenthurdle.routes'; +import EvaluationRoute from './routes/evaluation.routes'; +import UsersRoute from './routes/users.routes'; +import MetricsRouter from './routes/metrics.routes'; +import ReviewRouter from './routes/review.routes'; + +const allRoutes: Routes[] = [ + new AdminRoute(), + new UsersRoute(), + new AssessmentHurdleRoute(), + new ApplicationRoute(), + new EvaluationRoute(), + new MetricsRouter(), + new ReviewRouter(), +]; + +const port = parseInt(process.env.PORT) || 9000; +const app = new App(port, process.env.NODE_ENV || 'development', allRoutes); + +process.on('uncaughtException', err => { + console.log(JSON.stringify(err)); +}); +process.on('exit', () => { + console.log('exit'); + app?.cleanup(); +}); +process.on('SIGTERM', () => { + console.log('sigterm'); + + app?.cleanup(); +}); +app.listen(); diff --git a/api/src/services/admin.service.ts b/api/src/services/admin.service.ts new file mode 100644 index 0000000..75bfe99 --- /dev/null +++ b/api/src/services/admin.service.ts @@ -0,0 +1,13 @@ +import sequelize from 'sequelize'; +import { DBInterface } from '../database'; + +export default class AdminService { + async doHealthCheck(dbInstance: DBInterface): Promise { + const rst = await dbInstance.query('select 1+1 as result', { + type: sequelize.QueryTypes.SELECT, + raw: false, //don't have a model definition for your query + plain: true, //sequelize will only return the first + }); + return rst !== null && rst !== undefined; + } +} diff --git a/api/src/services/applicant.service.ts b/api/src/services/applicant.service.ts new file mode 100644 index 0000000..9160616 --- /dev/null +++ b/api/src/services/applicant.service.ts @@ -0,0 +1,449 @@ +import HttpException from '../exceptions/HttpException'; +import { isEmpty } from '../utils/isEmpty'; +import { logger } from '../utils/logger'; +import { Applicant } from '../models/applicant'; +import CreateApplicantDto, { CreateApplicantBulkDto } from '../dto/createapplicant.dto'; +// import ApplicantRecusalDto from '../dto/applicantrecusal.dto'; +import { ApplicantMeta } from '../models/applicant_meta'; +import { ApplicantRecusals } from '../models/applicant_recusals'; +import { ApplicationAssignments } from '../models/application_assignments'; +import BulkUSASApplicationsDto, { USASApplicationDto } from '../dto/BulkApplicantApplications.dto'; +import ApplicationService from './application.service'; +import SpecialtyService from './specialty.service'; +import CompetencyService from './competency.service'; +import CreateApplicationDto from '../dto/createapplication.dto'; +import { Application } from '../models/application'; +import { ApplicationEvaluation } from '../models/application_evaluation'; +import { Competency } from '../models/competency'; +import { Op, Transaction } from 'sequelize'; +import ApplicantDisplayDto, { CompetencyEvaluationDto, CompetencyWithSelectors } from '../dto/applicantdisplay.dto'; +import { SpecialtyCompetencies } from '../models/specialty_competencies'; +import { CompetencyEvaluation } from '../models/competency_evaluation'; +import { CompetencySelectors } from '../models/competency_selectors'; +import { Specialty } from '../models/specialty'; +import { ApplicantEvaluationFeedback } from '../models/applicant_evaluation_feedback'; +import { ApplicationEvaluationCompetency } from '../models/application_evaluation_competency'; +import { DBInterface } from '../database'; +import { CompetencyEvaluationCount } from '../models/competency_evaluation_count'; +import { ApplicantApplicationEvaluationNotes } from '../models/applicant_application_evaluation_notes'; +import marked from 'marked'; +marked.setOptions({ + renderer: new marked.Renderer(), + gfm: true, +}); +const converter = marked; + +export default class ApplicantService { + applicationService = new ApplicationService(); + specialtyService = new SpecialtyService(); + competencyService = new CompetencyService(); + + async getById(id: string): Promise { + const applicant = await Applicant.findByPk(id); + if (!applicant) throw new HttpException(404, 'Not Found'); + return applicant; + } + + async getByIdWithMeta(applicantId: string, hurdleId: string): Promise<[Applicant, ApplicantMeta]> { + if (isEmpty(applicantId) || isEmpty(hurdleId)) throw new HttpException(400, 'Applicant not found'); + const rst = await Applicant.findByPk(applicantId, { + //Applicant.associations.ApplicantMeta + include: [ApplicantMeta as any], + rejectOnEmpty: false, + }); + if (!rst) throw new HttpException(404, 'Not Found'); + + if (rst.assessment_hurdle_id !== hurdleId) { + throw new HttpException(403, `Applicant ${applicantId} does not below to AssessmentHurdle ${hurdleId}`); + } + + const meta = await rst.getApplicantMetum(); + return [rst, meta]; + } + + async getAll(): Promise { + const rst: Applicant[] = await Applicant.findAll(); + return rst; + } + + async bulkUpsert(body: CreateApplicantBulkDto, assessmentHurdleId: string): Promise { + const rst = await Promise.all(body.applicants.map(a => this.upsert(a, assessmentHurdleId))); + return rst; + } + + async upsert(body: CreateApplicantDto, assessmentHurdleId: string): Promise { + const [instance] = await Applicant.upsert({ + id: body.existingId, + assessment_hurdle_id: assessmentHurdleId, + flag_message: body.flagMessage, + flag_type: body.flagType, + name: body.name, + additional_note: body.additionalNote, + }); + + logger.debug(`Upserted Applicant ${instance.id}`); + const [meta, created] = await ApplicantMeta.findOrCreate({ + where: { applicant_id: instance.id }, + defaults: { + staffing_application_id: body.applicationId, + staffing_first_name: body.firstName, + staffing_last_name: body.lastName, + staffing_middle_name: body.middleName, + applicant_id: instance.id, + staffing_application_number: body.applicationNumber, + }, + }); + + if (!created) { + meta.update({ + staffing_application_id: body.applicationId, + staffing_first_name: body.firstName, + staffing_last_name: body.lastName, + staffing_middle_name: body.middleName, + applicant_id: instance.id, + staffing_application_number: body.applicationNumber, + }); + } + + logger.debug(`Upserted ApplicantMeta ${meta.id}`); + + return instance; + } + + async recuse(dbInstance: DBInterface, applicantId: string, evaluatorId: string): Promise { + const [ar, created] = await ApplicantRecusals.findOrCreate({ + where: { applicant_id: applicantId, recused_evaluator_id: evaluatorId }, + defaults: { + applicant_id: applicantId, + recused_evaluator_id: evaluatorId, + }, + }); + + if (!created) { + ar.update({ + applicant_id: applicantId, + recused_evaluator_id: evaluatorId, + }); + } + logger.debug(`Recusal by ${evaluatorId} for applicant: ${applicantId}, recordId: ${ar.id}`); + //remove all assignments across hurdles when a recusal happens + const [removedAssignments] = await ApplicationAssignments.update( + { + active: false, + }, + { + where: { applicant_id: ar.applicant_id, evaluator_id: ar.recused_evaluator_id }, + }, + ); + if (removedAssignments > 0) { + logger.debug(`Removed Assignment for ${ar.recused_evaluator_id} on ${ar.applicant_id}`); + } + + const existingEvaluations = await ApplicationEvaluation.findAll({ + where: { + evaluator: evaluatorId, + }, + include: [ + { + model: Application as any, + required: true, + where: { + applicant_id: applicantId, + }, + }, + { + model: ApplicationEvaluationCompetency as any, + required: true, + include: [ + { + model: CompetencyEvaluation as any, + required: true, + }, + ], + }, + ], + }); + if (existingEvaluations.length) { + logger.debug(`${existingEvaluations.length} existing evaluations to remove`); + + const totalDeleted = await dbInstance.transaction(async (t: Transaction) => { + let totalDeleted = 0; + + const feedbacksDeleted = await ApplicantEvaluationFeedback.destroy({ + where: { + applicant_id: applicantId, + evaluator_id: evaluatorId, + }, + }); + logger.debug(`[Transaction] ${feedbacksDeleted} ApplicantEvaluationFeedback to delete`); + totalDeleted += feedbacksDeleted; + + //same performance that deleting by Ids + for await (const evaluation of existingEvaluations) { + logger.debug(`[Transaction] ${evaluation.ApplicationEvaluationCompetencies.length} ApplicationEvaluationCompetencies to delete`); + const aecDeleted = await Promise.all(evaluation.ApplicationEvaluationCompetencies.map(element => element.destroy({ transaction: t }))); + totalDeleted += aecDeleted.length; + + const compsEvals = evaluation.ApplicationEvaluationCompetencies.map(aec => aec.CompetencyEvaluation); + logger.debug(`[Transaction] ${compsEvals.length} CompetencyEvaluation to delete`); + + const ceDeleted = await Promise.all(compsEvals.map(element => element.destroy({ transaction: t }))); + totalDeleted += ceDeleted.length; + + await evaluation.destroy({ transaction: t }); + totalDeleted += 1; + } + + return totalDeleted; + }); + logger.debug(`Removing evaluations deleted ${totalDeleted} records`); + } + + return ar; + } + + async flag(applicantId: string, flagType: number, message: string, userId?: string): Promise { + // const result: Applicant = await this.service.flag(applicantId, 1, `${evaluatorEmail}: ${flagMessage}`, flaggerId); + + const [number, updates] = await Applicant.update( + { + flag_message: message, + flag_type: flagType, + }, + { + where: { + id: applicantId, + }, + returning: true, + }, + ); + + if (number !== 1) throw new HttpException(500, 'Flagging Failed'); + const removedAssignments = await ApplicationAssignments.destroy({ + where: { applicant_id: applicantId, active: true, evaluator_id: userId }, + }); + if (removedAssignments > 0) { + logger.debug(`Removed all assignments for ${applicantId}`); + } + return updates[0]; + } + // This must be used with USAS due to the specialty `localId` === `applicationMetaRatingCombination` + async bulkUSASUpsert(body: BulkUSASApplicationsDto, hurdleId: string): Promise { + logger.debug(`BulkUSASUpsert: ${body.applications.length} applications`); + const specialties = await this.specialtyService.getAllByHurdleId(hurdleId); + const specialtyIdByLocalId: { [localId: string]: string } = specialties.reduce((memo: { [localId: string]: string }, specialty) => { + memo[specialty.local_id] = specialty.id; + return memo; + }, {}); + + const applicationsByApplicant = body.applications.reduce( + ( + memo: { + [usasNumber: string]: { + applicant: { + firstName: string; + lastName: string; + middleName: string; + staffingApplicationId: string; + staffingApplicationNumber: string; + }; + applications: { + staffingRatingId: string; + staffingAssessmentId: string; + staffingRatingCombination: string; + }[]; + }; + }, + application: USASApplicationDto, + ) => { + const { + firstName, + lastName, + middleName, + staffingApplicationId, + staffingApplicationNumber, + staffingAssessmentId, + staffingRatingCombination, + staffingRatingId, + } = application; + if (!specialtyIdByLocalId[staffingRatingCombination]) { + throw new HttpException(500, `Application ratingCombinations of ${staffingRatingCombination} does not match any specialty localId`); + } + if (staffingApplicationNumber in memo) { + memo[staffingApplicationNumber].applications.push({ + staffingRatingId, + staffingAssessmentId, + staffingRatingCombination, + }); + } else { + memo[staffingApplicationNumber] = { + applicant: { + firstName, + lastName, + middleName, + staffingApplicationId, + staffingApplicationNumber, + }, + applications: [ + { + staffingRatingId, + staffingAssessmentId, + staffingRatingCombination, + }, + ], + }; + } + return memo; + }, + {}, + ); + + const vals = Object.values(applicationsByApplicant); + + logger.debug(`BulkUSASUpsert: ${vals.length} applications by applicants`); + const applicants = await Promise.all( + vals.map(async ({ applicant, applications }) => { + const applicantDTO = new CreateApplicantDto(); + applicantDTO.assessmentHurdleId = hurdleId; + applicantDTO.firstName = applicant.firstName; + applicantDTO.lastName = applicant.lastName; + applicantDTO.middleName = applicant.middleName; + applicantDTO.name = `${applicant.firstName} ${applicant.middleName} ${applicant.lastName}`; + applicantDTO.applicationNumber = applicant.staffingApplicationId; + applicantDTO.applicationId = applicant.staffingApplicationId; + const applicantWithId = await this.upsert(applicantDTO, hurdleId); + logger.debug(`BulkUSASUpsert: Created ${applicantWithId.id} applicant`); + await Promise.all( + applications.map(application => { + const { staffingRatingCombination, staffingRatingId, staffingAssessmentId } = application; + const applicationDto = new CreateApplicationDto(); + applicationDto.applicantId = applicantWithId.id; + applicationDto.specialtyId = specialtyIdByLocalId[staffingRatingCombination]; + + applicationDto.applicationMetaRatingId = staffingRatingId; + applicationDto.applicationMetaAssessmentId = staffingAssessmentId; + applicationDto.applicationMetaRatingCombination = staffingRatingCombination; + applicationDto.applicationMetaId = staffingRatingId; + logger.debug(`BulkUSASUpsert: Creating application for ${applicant.staffingApplicationNumber} for ${applicationDto.specialtyId}`); + this.applicationService.upsert(applicationDto); + }), + ); + return applicantWithId; + }), + ); + logger.debug(`BulkUSASUpsert: ${applicants} total applicants`); + return applicants; + } + /** + * Require: + * application_evaluation.evaluation_note + * applicant_evaluation_feedback.evaluation_feedback + * competency.* + * competency_selectors.* + * competency_evaluation.evaluation_note + * competency_evaluation.competency_selector_id + */ + async getDisplay(applicantId: string, currentUserId: string): Promise { + if (isEmpty(applicantId)) throw new HttpException(400, 'Empty Parameters'); + + const applicant = await Applicant.findByPk(applicantId); + + if (!applicant) throw new HttpException(404, `applicant ${applicantId} not found`); + + // logger.debug(`getDisplay for ${applicantId} as currentUser ${currentUserId}`); + + // const applications = applicant.Applications!; + + // const specialties = applications + // .flatMap(a => a.Specialty) + // .filter((val, idx, arr) => { + // return arr.indexOf(val) === idx; + // }); + // const specialtyIds = specialties.map(s => s.id); + + // const applicationEvaluationIds = applications + // .flatMap(a => a.ApplicationEvaluations) + // .filter((val, idx, arr) => { + // return arr.indexOf(val) === idx; + // }) + // .map(a => a.id); + + // const specCompMap = await SpecialtyCompetencies.findAll({ + // where: { + // specialty_id: { + // [Op.in]: specialtyIds, + // }, + // }, + // include: [ + // { + // model: Competency as any, + // required: true, + // include: [CompetencySelectors as any], + // }, + // ], + // }); + + // const competencyIds = specCompMap.map(scm => scm.competency_id!); + + // const existingEvals = await ApplicationEvaluationCompetency.findAll({ + // where: { + // application_evaluation_id: { + // [Op.in]: applicationEvaluationIds, + // }, + // }, + // include: [ + // { + // model: CompetencyEvaluation as any, + // required: false, + // where: { + // competency_id: { + // [Op.in]: competencyIds, + // }, + // evaluator: currentUserId, + // }, + // }, + // ], + // }); + + const feedback = await ApplicantEvaluationFeedback.findOne({ + where: { + applicant_id: applicantId, + evaluator_id: currentUserId, + }, + }); + + const { competencies, evaluatedCompetencies, specialtyMap, specialties, isTieBreaker, competencyJustifications } = + await this.competencyService.getAllActiveForApplicant(currentUserId, applicantId, applicant.assessment_hurdle_id!); + + // let applicantNotes = [] as string[]; + // // if (isTieBreaker) { + // // applicantNotes = ( + // // await ApplicantApplicationEvaluationNotes.findAll({ + // // where: { applicant_id: applicantId }, + // // }) + // // ) + // // .map(applicantNotes => { + // // return applicantNotes.evaluation_note; + // // }) + // // .filter(e => e) as string[]; + // } + + const specialtyCompetencyMap = Object.entries(specialtyMap).reduce((memo, [sid, cSet]) => { + memo[sid] = Array.from(cSet); + return memo; + }, {} as { [specialtyId: string]: string[] }); + + const dto = new ApplicantDisplayDto(); + dto.applicant = applicant; + dto.applicantNotes = []; + dto.specialties = specialties; + dto.feedback = feedback?.evaluation_feedback || ''; + dto.feedbackTimestamp = feedback?.updated_at || new Date(); + dto.competencies = Object.values(competencies); + dto.specialtyCompetencyMap = specialtyCompetencyMap; + dto.competencyEvaluations = evaluatedCompetencies; + dto.isTieBreaker = isTieBreaker; + dto.competencyJustifications = competencyJustifications; + return dto; + } +} diff --git a/api/src/services/application.service.ts b/api/src/services/application.service.ts new file mode 100644 index 0000000..c93c729 --- /dev/null +++ b/api/src/services/application.service.ts @@ -0,0 +1,90 @@ +import CreateApplicationDto from '../dto/createapplication.dto'; +import HttpException from '../exceptions/HttpException'; +import { Application } from '../models/application'; +import { ApplicationMeta } from '../models/application_meta'; +import { isEmpty } from '../utils/isEmpty'; +import { logger } from '../utils/logger'; + +export default class ApplicationService { + async getById(id: string): Promise { + if (isEmpty(id)) throw new HttpException(400, "You're not ApplicationId"); + const rst = await Application.findByPk(id); + if (!rst) throw new HttpException(404, 'Not Found'); + return rst; + } + async getByIdWithMeta(id: string): Promise<[Application, ApplicationMeta]> { + if (isEmpty(id)) throw new HttpException(400, "You're not ApplicationId"); + const rst = await Application.findByPk(id, { include: [ApplicationMeta as any], rejectOnEmpty: false }); + if (!rst) throw new HttpException(404, 'Not Found'); + const meta = await rst.getApplicationMetum(); + return [rst, meta]; + } + + async getAllByApplicantId(id: string): Promise { + if (isEmpty(id)) throw new HttpException(400, 'Applicant not found'); + const rst: Application[] = await Application.findAll({ + where: { applicant_id: id }, + order: [['created_at', 'DESC']], + }); + return rst; + } + + async getAllBySpecialtyId(id: string): Promise { + if (isEmpty(id)) throw new HttpException(400, 'Applicant or specialty not found'); + const rst: Application[] = await Application.findAll({ + where: { specialty_id: id }, + order: [['created_at', 'DESC']], + }); + return rst; + } + + async upsert(body: CreateApplicationDto): Promise { + const [instance] = await Application.upsert({ + id: body.existingId, + applicant_id: body.applicantId, + specialty_id: body.specialtyId, + }); + logger.debug(`Upserted Application ${instance.id}`); + + const [meta, created] = await ApplicationMeta.findOrCreate({ + where: { application_id: instance.id }, + defaults: { + staffing_application_rating_id: body.applicationMetaId, + staffing_assessment_id: body.applicationMetaAssessmentId, + staffing_rating_combination: body.applicationMetaRatingCombination, + application_id: instance.id, + }, + }); + + if (!created) { + meta.update({ + staffing_application_rating_id: body.applicationMetaId, + staffing_assessment_id: body.applicationMetaAssessmentId, + staffing_rating_combination: body.applicationMetaRatingCombination, + application_id: instance.id, + }); + } + + logger.debug(`Upserted ApplicationMeta ${meta.id}`); + + return instance; + } + + async createMapping(applicantId: string, specialtyId: string): Promise { + if (isEmpty(applicantId) || isEmpty(specialtyId)) throw new HttpException(400, 'Applicant or specialty not found'); + const [created] = await Application.findOrCreate({ + where: { applicant_id: applicantId, specialty_id: specialtyId }, + defaults: { + applicant_id: applicantId, + specialty_id: specialtyId, + }, + }); + return created; + } + + async deleteMappingById(id: string): Promise { + if (isEmpty(id)) throw new HttpException(400, "You're not ApplicationId"); + const rst = await Application.destroy(); + return rst > 0; + } +} diff --git a/api/src/services/application_assignment.service.ts b/api/src/services/application_assignment.service.ts new file mode 100644 index 0000000..af1ae0f --- /dev/null +++ b/api/src/services/application_assignment.service.ts @@ -0,0 +1,202 @@ +import { Op } from 'sequelize'; +import CreateApplicationAssignmentDto from '../dto/createapplicationassignment.dto'; +import HttpException from '../exceptions/HttpException'; +import { Applicant } from '../models/applicant'; +import { ApplicationAssignments } from '../models/application_assignments'; +import { ApplicantEvaluationFeedback } from '../models/applicant_evaluation_feedback'; +import { ApplicantRecusals } from '../models/applicant_recusals'; +import { ApplicantQueue } from '../models/applicant_queue'; + +import { isEmpty } from '../utils/isEmpty'; +import { logger } from '../utils/logger'; +import sequelize from 'sequelize'; + +export default class ApplicationAssignmentService { + async getAll(): Promise { + const rst: ApplicationAssignments[] = await ApplicationAssignments.findAll(); + return rst; + } + + async getByApplicantId(id: string): Promise { + if (isEmpty(id)) throw new HttpException(400, 'Applicant not found'); + + const rst: ApplicationAssignments[] = await ApplicationAssignments.findAll({ + where: { applicant_id: id }, + }); + return rst; + } + + async getByEvaluatorId(id: string, hurdleId: string): Promise { + if (isEmpty(id)) throw new HttpException(400, "You're not EvaluatorId"); + const rst: ApplicationAssignments[] = await ApplicationAssignments.findAll({ + where: { evaluator_id: id, assessment_hurdle_id: hurdleId }, + }); + return rst; + } + + async upsert(body: CreateApplicationAssignmentDto): Promise { + const [rst, created] = await ApplicationAssignments.upsert( + { + id: body.existingId, + applicant_id: body.applicantId, + evaluator_id: body.evaluatorId, + active: body.active, + assessment_hurdle_id: body.assessmentHurdleId!, + }, + { returning: true }, + ); + logger.debug(`Upserted ${rst.id} application_assignment`); + return rst; + } + + async nextQueue(hurdleId: string, currentUserId: string): Promise<{ applicant_id: string; assessment_hurdle_id: string } | null> { + if (isEmpty(hurdleId) || isEmpty(currentUserId)) throw new HttpException(403, `Empty Parameters Submitted`); + // Check for applicants currently assigned + + const assingedApplicant = await ApplicationAssignments.findOne({ + where: { + evaluator_id: currentUserId, + active: true, + assessment_hurdle_id: hurdleId, + }, + include: [ + { + model: Applicant as any, + required: true, + attributes: ['id'], + include: [{ model: ApplicantEvaluationFeedback as any, required: false, attributes: ['evaluation_feedback'] }], + }, + ], + attributes: ['applicant_id', 'assessment_hurdle_id'], + order: [ + [Applicant, ApplicantEvaluationFeedback, 'evaluation_feedback', 'ASC NULLS LAST'], + ['updated_at', 'DESC'], + ], + }); + + if (assingedApplicant) { + // If there is an assigned applicant, return that + logger.debug(`Existing Active item in Queue for user: ${currentUserId} returning existing item ${assingedApplicant.applicant_id}`); + return { applicant_id: assingedApplicant.applicant_id, assessment_hurdle_id: assingedApplicant.assessment_hurdle_id }; + } + /** + * There are no active assignments. + * + * + * 1. Find all applicants in the pending list + * 2. Find all the flagged applicants + * 3. Find all the applicants this evaluator has recused from + * 4. Find all the applicants that have ever been reviewed by this evaluator + * + */ + + // + + const applicantsUnderReviewByAnyPromise = ApplicationAssignments.findAll({ + attributes: ['applicant_id'], + where: { + active: true, + assessment_hurdle_id: hurdleId, + }, + }).then(a => a.map(a => a.applicant_id)); + + const flaggedApplicantsPromise = Applicant.findAll({ + attributes: ['id'], + where: { + flag_type: { + [Op.not]: 0, + }, + assessment_hurdle_id: hurdleId, + }, + }).then(a => a.map(a => a.id)); + + const recusedFromApplicantsPromise = ApplicantRecusals.findAll({ + attributes: ['applicant_id'], + where: { + recused_evaluator_id: currentUserId, + }, + }).then(a => a.map(a => a.applicant_id!)); + + const allAssignedApplicantsPromise = ApplicationAssignments.findAll({ + where: { + evaluator_id: currentUserId, + assessment_hurdle_id: hurdleId, + }, + attributes: ['applicant_id'], + }).then(a => a.map(a => a.applicant_id!)); + const [applicantsUnderReviewByAny, flaggedApplicants, recusedFromApplicants, allAssignedApplicants] = await Promise.all([ + applicantsUnderReviewByAnyPromise, + flaggedApplicantsPromise, + recusedFromApplicantsPromise, + allAssignedApplicantsPromise, + ]); + + /** + * Once you have the pending, flagged, recused, and evaluated create the applicant filters + */ + const baseApplicantFilter = allAssignedApplicants.concat(flaggedApplicants, recusedFromApplicants); + const applicantFilterWithActiveReviewApplicants = baseApplicantFilter.concat(applicantsUnderReviewByAny); + + /** + * Try to find applicants based off filters + */ + let nextAssignment = await this.getFreshApplicant(hurdleId, applicantFilterWithActiveReviewApplicants); + + if (!nextAssignment) { + nextAssignment = await this.getFreshApplicant(hurdleId, baseApplicantFilter); + } + if (!nextAssignment) { + return null; + } + + logger.debug(`Next Assignment for ${currentUserId} in ${hurdleId} is ${nextAssignment}`); + return await this.upsertApplicationAssignment(nextAssignment!, currentUserId, hurdleId, true); + } + + async upsertApplicationAssignment(applicant_id: string, evaluator_id: string, assessment_hurdle_id: string, active: boolean) { + logger.debug(`Adding assignment for ${evaluator_id} in ${assessment_hurdle_id} is ${applicant_id}`); + + const [assignment] = await ApplicationAssignments.upsert( + { + applicant_id: applicant_id, + evaluator_id: evaluator_id, + assessment_hurdle_id: assessment_hurdle_id, + active: active, + }, + { returning: true }, + ); + + logger.debug(`Created Assignment ${assignment.id}`); + + return { applicant_id: assignment.applicant_id, assessment_hurdle_id: assignment.assessment_hurdle_id }; + } + + async getFreshApplicant(hurdleId: string, filterOutApplicants: string[]) { + // TODO: + // We've seen Sequelize fall over with long/nested models before. Using the previous exclusion method could result in + // abberant behavior in a medium sized hiring action - this should be redone as a few sets of raw queries + + const freshApplicant = await ApplicantQueue.findOne({ + attributes: [ + 'applicant_id', + [ + sequelize.literal(`(SELECT COUNT(*) + FROM application_assignments as assn + WHERE assn."applicant_id" = "ApplicantQueue"."applicant_id" + AND active IS true)`), + 'current_queue_count', + ], + ], + where: { + assessment_hurdle_id: hurdleId, + applicant_id: { [Op.notIn]: filterOutApplicants }, + }, + group: ['ApplicantQueue.applicant_id', 'evaluators'], + order: sequelize.literal('current_queue_count ASC NULLS FIRST, evaluators DESC NULLS LAST, applicant_id DESC'), + }); + if (!freshApplicant) { + return null; + } + return freshApplicant.applicant_id; + } +} diff --git a/api/src/services/assessmenthurdle.service.ts b/api/src/services/assessmenthurdle.service.ts new file mode 100644 index 0000000..9e5fcbc --- /dev/null +++ b/api/src/services/assessmenthurdle.service.ts @@ -0,0 +1,262 @@ +import { Op } from 'sequelize'; + +import HttpException from '../exceptions/HttpException'; +import { isEmpty } from '../utils/isEmpty'; +import { logger } from '../utils/logger'; + +import { AssessmentHurdle } from '../models/assessment_hurdle'; +import { AssessmentHurdleMeta } from '../models/assessment_hurdle_meta'; +import { Applicant } from '../models/applicant'; +import { Application } from '../models/application'; +import { AssessmentHurdleUser } from '../models/assessment_hurdle_user'; +import { ApplicationEvaluation } from '../models/application_evaluation'; +import { Competency } from '../models/competency'; + +import CreateAssessmentHurdleDto from '../dto/createassessmenthurdle.dto'; +import HrDisplayDto, { ApplicationHrEvaluationDto, CompetencyHrEvaluationDto } from '../dto/hrdisplay.dto'; +import { ApplicationEvaluationCompetency } from '../models/application_evaluation_competency'; +import { ApplicantEvaluationFeedback } from '../models/applicant_evaluation_feedback'; +import { CompetencyEvaluation } from '../models/competency_evaluation'; +import { CompetencySelectors } from '../models/competency_selectors'; + +export default class AssessmentHurdleService { + async getAll(): Promise { + const rst: AssessmentHurdle[] = await AssessmentHurdle.findAll(); + return rst; + } + async getAllWithUserRoles(userId: string): Promise { + const rst: AssessmentHurdle[] = await AssessmentHurdle.findAll({ + include: [ + { + model: AssessmentHurdleUser as any, + required: true, + where: { app_user_id: userId }, + attributes: ['app_user_id', 'role'], + }, + ], + }); + return rst; + } + + async getById(id: string): Promise { + if (isEmpty(id)) throw new HttpException(400, "You're not AssessmentHurdleId"); + + const rst = await AssessmentHurdle.findByPk(id, { include: [{ all: true }] }); + if (!rst) throw new HttpException(404, 'Not Found'); + return rst; + } + + async upsert(body: CreateAssessmentHurdleDto): Promise { + const [instance] = await AssessmentHurdle.upsert({ + id: body.existingId, + department_name: body.departmentName, + component_name: body.componentName, + position_name: body.positionName, + position_details: body.positionDetails, + locations: body.locations, + start_datetime: body.startDatetime, + end_datetime: body.endDatetime, + hurdle_display_type: body.hurdleDisplayType, + evaluations_required: body.evaluationsRequired, + hr_name: body.hrName, + hr_email: body.hrEmail, + assessment_name: body.assessmentName, + }); + logger.debug(`Upserted AssessmentHurdle: ${instance.id}`); + const [meta, created] = await AssessmentHurdleMeta.findOrCreate({ + where: { assessment_hurdle_id: instance.id }, + defaults: { + staffing_assessment_id: body.assessmentId, + staffing_vacancy_id: body.vacancyId, + assessment_hurdle_id: instance.id, + staffing_fail_nor: body.failNor, + staffing_pass_nor: body.passNor, + }, + }); + + if (!created) { + await meta.update({ + staffing_assessment_id: body.assessmentId, + staffing_vacancy_id: body.vacancyId, + staffing_fail_nor: body.failNor, + staffing_pass_nor: body.passNor, + }); + } + + logger.debug(`Upserted AssessmentMeta ${meta.id}`); + + return instance; + } + + async getHrDisplay(hurdleId: string, currentUserId: string): Promise { + if (isEmpty(hurdleId) || isEmpty(currentUserId)) throw new HttpException(400, 'Empty Parameters'); + + const assessmentHurdle = await AssessmentHurdle.findByPk(hurdleId, { + include: [ + { + model: Applicant as any, + required: true, + where: { + flag_type: 0, + }, + include: [ + { + model: Application as any, + required: true, + }, + ], + }, + ], + }); + + if (!assessmentHurdle) throw new HttpException(404, `${hurdleId} not found`); + + const applicantsById = assessmentHurdle.Applicants.reduce((memo, a) => { + memo[a.id] = a.name!; + return memo; + }, {} as { [name: string]: string }); + + const applicantByApplicationId = assessmentHurdle.Applicants.flatMap(a => a.Applications).reduce((memo, a) => { + memo[a.id] = { applicantName: applicantsById[a.applicant_id!], applicantId: a.applicant_id! }; + return memo; + }, {} as { [applicationId: string]: { applicantId: string; applicantName: string } }); + + // get all application evaluations + const applicationEvaluations = await ApplicationEvaluation.findAll({ + where: { + application_id: Object.keys(applicantByApplicationId), + }, + attributes: ['evaluation_note', 'evaluator', 'id', 'approved', 'application_id', 'created_at'], + include: [ + { + model: ApplicationEvaluationCompetency as any, + required: false, + separate: true, + attributes: ['application_evaluation_id', 'competency_evaluation_id'], + include: [{ model: CompetencyEvaluation as any, required: true }], + }, + ], + }); + + const applicantFeedback = ( + await ApplicantEvaluationFeedback.findAll({ + where: { + applicant_id: Object.values(applicantByApplicationId).map(a => a.applicantId), + }, + }) + ).reduce((memo, aef) => { + const applicantEvaluationKey = aef.applicant_id! + aef.evaluator_id; + memo[applicantEvaluationKey] = aef; + return memo; + }, {} as { [applicantEvaluationKey: string]: ApplicantEvaluationFeedback }); + + const competencyNamesById = ( + await Competency.findAll({ + where: { + assessment_hurdle_id: hurdleId, + }, + attributes: ['name', 'sort_order', 'id'], + }) + ).reduce((memo, c) => { + memo[c.id] = { name: c.name, sort_order: c.sort_order! }; + return memo; + }, {} as { [competencyId: string]: { name: string; sort_order: number } }); + const selectorsById = ( + await CompetencySelectors.findAll({ + where: { + competency_id: Object.keys(competencyNamesById), + }, + attributes: ['id', 'display_name'], + }) + ).reduce((memo, s) => { + memo[s.id] = s.display_name!; + return memo; + }, {} as { [selectorId: string]: string }); + + const dto = new HrDisplayDto(); + + dto.applicantEvaluations = ( + await Promise.all( + applicationEvaluations.map(async ae => { + const applicantId = applicantByApplicationId[ae.application_id!].applicantId; + const evaluator = ae.evaluator!; + const applicantEvaluationKey = applicantId + evaluator; + + const applicationEvaluation = {} as ApplicationHrEvaluationDto; + applicationEvaluation.applicantEvaluationKey = applicantEvaluationKey; + applicationEvaluation.approved = ae.approved!; + applicationEvaluation.applicationIds = [ae.id]; + applicationEvaluation.evaluation_note = ae.evaluation_note!; + applicationEvaluation.evaluator = evaluator; + applicationEvaluation.created_at = ae.created_at!.toLocaleDateString('en-US', { + month: 'long', + day: 'numeric', + year: 'numeric', + hour: 'numeric', + minute: 'numeric', + hour12: true, + }); + applicationEvaluation.applicantName = applicantByApplicationId[ae.application_id!].applicantName; + applicationEvaluation.applicantId = applicantId; + + applicationEvaluation.competencyEvaluations = ( + await Promise.all(ae.ApplicationEvaluationCompetencies.flatMap(async aec => await aec.getCompetencyEvaluation())) + ) + .map(ce => { + const competencyEvaluation = {} as CompetencyHrEvaluationDto; + competencyEvaluation.competency_name = competencyNamesById[ce.competency_id].name; + competencyEvaluation.sort_order = competencyNamesById[ce.competency_id].sort_order; + competencyEvaluation.evaluation_note = ce.evaluation_note; + competencyEvaluation.competency_selector_name = selectorsById[ce.competency_selector_id]; + competencyEvaluation.id = ce.id; + return competencyEvaluation; + }) + .sort((a, b) => a.sort_order - b.sort_order); + return applicationEvaluation; + }), + ) + ).reduce((memo, applicationEvaluation) => { + const { applicantEvaluationKey } = applicationEvaluation; + + if (!(applicantEvaluationKey in memo)) { + let applicantFeedbackId = ''; + let evaluationFeedback = ''; + if (applicantEvaluationKey in applicantFeedback) { + applicantFeedbackId = applicantFeedback[applicantEvaluationKey].id; + evaluationFeedback = applicantFeedback[applicantEvaluationKey].evaluation_feedback || ''; + } + + applicationEvaluation.feedback = { + applicantFeedbackId: applicantFeedbackId, + evaluationFeedback: evaluationFeedback, + }; + memo[applicantEvaluationKey] = applicationEvaluation; + return memo; + } + + const { competencyEvaluations, applicationIds } = memo[applicantEvaluationKey]; + memo[applicantEvaluationKey].applicationIds.push(applicationIds[0]); + + const allCompetencies = [...new Set(competencyEvaluations.concat(competencyEvaluations))]; + + memo[applicantEvaluationKey].competencyEvaluations = allCompetencies; + return memo; + }, {} as { [applicantEvaluationKey: string]: ApplicationHrEvaluationDto }); + + dto.assessmentHurdle = assessmentHurdle; + + // = assessmentHurdle.Applicants.flatMap(a => a.Applications).flatMap(a => a.ApplicationEvaluations); + + dto.flaggedApplicants = await Applicant.findAll({ + where: { + assessment_hurdle_id: hurdleId, + flag_type: { + [Op.ne]: 0, + }, + }, + include: [Application as any], + }); + + return dto; + } +} diff --git a/api/src/services/authorization.service.ts b/api/src/services/authorization.service.ts new file mode 100644 index 0000000..c58e119 --- /dev/null +++ b/api/src/services/authorization.service.ts @@ -0,0 +1,24 @@ +import { AssessmentHurdleUser } from '../models/assessment_hurdle_user'; +export default class AuthorizationService { + isAuthorizedForAssessmentHurdle = async (assessmentHurdleId: string, userId: string) => { + const rst = await AssessmentHurdleUser.findOne({ + attributes: ['id'], + where: { + assessment_hurdle_id: assessmentHurdleId, + app_user_id: userId, + }, + }); + return rst; + }; + isAuthorizedForRoleOnAssessmentHurdle = async (requiredRole: number, assessmentHurdleId: string, userId: string) => { + const rst = await AssessmentHurdleUser.findOne({ + attributes: ['id'], + where: { + assessment_hurdle_id: assessmentHurdleId, + app_user_id: userId, + role: requiredRole, + }, + }); + return rst; + }; +} diff --git a/api/src/services/competency.service.ts b/api/src/services/competency.service.ts new file mode 100644 index 0000000..1ad0f85 --- /dev/null +++ b/api/src/services/competency.service.ts @@ -0,0 +1,309 @@ +import { Op } from 'sequelize'; +import { CompetencyEvaluationDto, CompetencyJustification, CompetencyWithSelectors } from '../dto/applicantdisplay.dto'; +import CreateCompetenciesDto from '../dto/createcompetencies.dto'; +import CreateCompetencyDto from '../dto/createcompetency.dto'; +import HttpException from '../exceptions/HttpException'; +import { Applicant } from '../models/applicant'; +import { Application } from '../models/application'; +import { ApplicationEvaluation } from '../models/application_evaluation'; +import { ApplicationEvaluationCompetency } from '../models/application_evaluation_competency'; +import { AssessmentHurdle } from '../models/assessment_hurdle'; +import { Competency } from '../models/competency'; +import { CompetencyEvaluation } from '../models/competency_evaluation'; +import { CompetencyEvaluationCount } from '../models/competency_evaluation_count'; +import { CompetencySelectors } from '../models/competency_selectors'; +import { Specialty } from '../models/specialty'; +import { SpecialtyCompetencies } from '../models/specialty_competencies'; +import { isEmpty } from '../utils/isEmpty'; +import { logger } from '../utils/logger'; +import marked from 'marked'; + +marked.setOptions({ + renderer: new marked.Renderer(), + gfm: true, +}); +const converter = marked; + +export default class CompetencyService { + async getById(id: string): Promise { + if (isEmpty(id)) throw new HttpException(400, 'No competency id provided'); + const rst = await Competency.findByPk(id); + if (!rst) throw new HttpException(404, 'Not Found'); + return rst; + } + async getAll(): Promise { + const rst: Competency[] = await Competency.findAll(); + return rst; + } + async getAllByHurdleId(hurdleId: string): Promise { + if (isEmpty(hurdleId)) throw new HttpException(400, 'No hurdle id provided'); + const rst: Competency[] = await Competency.findAll({ + where: { + assessment_hurdle_id: hurdleId, + }, + }); + if (!rst) throw new HttpException(404, 'No competencies found'); + return rst; + } + // TODO: Prune if no clear use. + async getAllMappingsById(id: string): Promise { + if (isEmpty(id)) throw new HttpException(400, 'No competency id provided'); + const rst: SpecialtyCompetencies[] = await SpecialtyCompetencies.findAll({ where: { competency_id: id }, include: { all: true } }); + if (!rst) throw new HttpException(404, 'Not Found'); + return rst; + } + + async upsert(body: CreateCompetencyDto, assessmentHurdleId: string): Promise { + const [instance] = await Competency.upsert({ + id: body.existingId, + definition: (body.definition && converter.parse(body.definition)) || '', + name: body.name, + required_proficiency_definition: (body.requiredProficiencyDefinition && converter.parse(body.requiredProficiencyDefinition)) || '', + display_type: body.displayType, + screen_out: body.screenOut, + local_id: body.localId, + assessment_hurdle_id: assessmentHurdleId, + sort_order: body.sortOrder, + }); + + logger.debug(`Created Competency: ${instance.id}`); + + if (body.specialtyIds) { + await Promise.all( + body.specialtyIds.map(async sid => { + const mapping = await SpecialtyCompetencies.create({ + competency_id: instance.id, + specialty_id: sid, + }); + logger.debug(`Added Mapping ${mapping.id} to Specialty: ${sid}`); + }), + ); + } + + if (body.selectors.length) { + const meetsMapping = await Promise.all( + body.selectors.map(async m => { + const rst = await CompetencySelectors.create({ + competency_id: instance.id, + display_name: m.displayName, + point_value: m.pointValue, + id: m.existingId, + sort_order: m.sortOrder, + default_text: m.defaultText, + }); + return rst; + }), + ); + logger.debug(`Upserted ${meetsMapping.length} CompetencySelectors from ${body.selectors.length} inputs`); + } + + return instance; + } + async upsertAll(body: CreateCompetenciesDto, assessmentHurdleId: string): Promise { + return await Promise.all( + body.competencies.map(comp => { + return this.upsert({ ...comp }, assessmentHurdleId); + }), + ); + } + async getAllCompetenciesWithMap(applicantId: string) { + // Get all specialty competencies applied for by applicant: + const applications = await Application.findAll({ + where: { + applicant_id: applicantId, + }, + include: [ + { + model: Specialty as any, + required: true, + attributes: ['id'], + }, + // { + // model: ApplicationEvaluation as any, + // required: false, + // attributes: ['id'], + // where: { + // evaluator: evaluator, + // }, + // }, + ], + }); + if (!applications.length) throw new HttpException(404, `No applications found for ${applicantId}`); + + const specialties = applications.map(a => a.Specialty); + const specialtyIds = applications.map(s => s.specialty_id!); + + // Get all the specialtyCompetency combinations + const competenciesBySpecialty = await SpecialtyCompetencies.findAll({ + where: { + specialty_id: specialtyIds, + }, + include: [ + { + model: Competency as any, + required: true, + include: [ + { + model: CompetencySelectors as any, + required: true, + }, + ], + }, + ], + }); + // Map them out for use + const specialtyMap = {} as { [specialtyId: string]: Set }; + // this is for removing competencies later... + const competencyMap = {} as { [competencyId: string]: Set }; + const competencies = {} as { [competencyId: string]: CompetencyWithSelectors }; + + competenciesBySpecialty.forEach(specComp => { + const { specialty_id: specialtyId, competency_id: competencyId, Competency: competency } = specComp; + const { + CompetencySelectors: selectors, + id, + name, + local_id, + assessment_hurdle_id, + definition, + required_proficiency_definition, + display_type, + screen_out, + updated_at, + sort_order, + } = competency; + if (!(specialtyId! in specialtyMap)) { + specialtyMap[specialtyId!] = new Set(); + } + specialtyMap[specialtyId!].add(competencyId!); + + if (!(competencyId! in competencyMap)) { + competencyMap[competencyId!] = new Set(); + } + competencyMap[competencyId!].add(specialtyId!); + + if (!(competencyId! in competencies)) { + competencies[competencyId!] = { + selectors, + id, + name, + local_id, + assessment_hurdle_id: assessment_hurdle_id!, + definition, + required_proficiency_definition: required_proficiency_definition!, + display_type: display_type!, + screen_out: screen_out!, + updated_at: updated_at!, + sort_order: sort_order!, + }; + } + }); + + return { specialties, specialtyMap, competencies, competencyMap }; + } + async getAllActiveForApplicant(evaluator: string, applicantId: string, assessmentHurdleId: string) { + const assessmentHurdle = await AssessmentHurdle.findOne({ + attributes: ['id', 'hurdle_display_type', 'evaluations_required', 'require_review_for_all_passing'], + where: { id: assessmentHurdleId }, + }); + const { evaluations_required: evaluationsRequired } = assessmentHurdle!; + const { specialties, specialtyMap, competencies, competencyMap } = await this.getAllCompetenciesWithMap(applicantId); + + const evaluatedCompetencies = ( + await CompetencyEvaluation.findAll({ + where: { + competency_id: Object.keys(competencies), + evaluator: evaluator, + applicant: applicantId, + }, + }) + ).reduce((memo, c) => { + const { id: competencyEvaluationId, competency_id, competency_selector_id, evaluation_note, evaluator, updated_at } = c!; + memo[competency_id] = { + competencyEvaluationId, + competency_id, + competency_selector_id, + evaluation_note, + evaluator, + updated_at: updated_at!, + }; + return memo; + }, {} as { [competencyId: string]: CompetencyEvaluationDto }); + + // Get current competency Counts + const currentCompetencyCounts = ( + await CompetencyEvaluationCount.findAll({ + attributes: ['competency_id', 'does_not_meet', 'meets'], + where: { applicant: applicantId }, + }) + ).reduce( + (competencyEvaluationCounts, comp) => { + competencyEvaluationCounts[comp.competency_id!] = comp; + return competencyEvaluationCounts; + }, + {} as { + [competencyId: string]: CompetencyEvaluationCount; + }, + ); + + const removeCompetencies = [] as string[]; + // Create Screen out competency list: + // Only competencies that _haven't_ been evaluated previously + // or competencies that have >= evaluationsRequired should be removed. + + // These counts are needed for the situations where there is disagreement + // on _all_ competencies. This can be removed when `evaluation_types` are + // instituted. + + let isTieBreaker = false; + Object.keys(competencies).forEach(cid => { + if (cid in evaluatedCompetencies) { + return; + } + const doesNotMeet = +(currentCompetencyCounts[cid]?.does_not_meet || 0); + const meets = +(currentCompetencyCounts[cid]?.meets || 0); + + if (doesNotMeet < evaluationsRequired && meets < evaluationsRequired) { + if (doesNotMeet + meets >= evaluationsRequired) { + isTieBreaker = true; + } + return; + } + isTieBreaker = true; + removeCompetencies.push(cid); + }); + removeCompetencies.forEach(c => { + if (c in competencyMap) { + competencyMap[c].forEach(s => { + specialtyMap[s].delete(c); + }); + } + delete competencies[c]; + delete competencyMap[c]; + }); + + let competencyJustifications = [] as CompetencyJustification[]; + if (isTieBreaker) { + competencyJustifications = ( + await CompetencyEvaluation.findAll({ + where: { + competency_id: Object.keys(competencies), + applicant: applicantId, + evaluator: { [Op.not]: evaluator }, + }, + }) + ).map((c): CompetencyJustification => { + const { id, competency_selector_id, competency_id, evaluator: local_evaluator, evaluation_note, updated_at } = c; + return { + competencyEvaluationId: id, + justification: evaluation_note, + evaluator: local_evaluator, + competency_id: competency_id, + competency_selector_id: competency_selector_id, + updated_at: updated_at!, + }; + }); + } + return { competencies, evaluatedCompetencies, specialtyMap, specialties, isTieBreaker, competencyJustifications }; + } +} diff --git a/api/src/services/evaluation.service.ts b/api/src/services/evaluation.service.ts new file mode 100644 index 0000000..74783b4 --- /dev/null +++ b/api/src/services/evaluation.service.ts @@ -0,0 +1,516 @@ +import { Transaction } from 'sequelize/types'; +import { DBInterface } from '../database'; +import { EvaluationApplicationFeedbackDto } from '../dto/evaluationfeedback.dto'; +import { EvaluationApplicationReviewSubmitDto } from '../dto/evaluationreviewsubmit.dto'; +import EvaluationSubmitDto from '../dto/evaluationsubmit.dto'; +import HttpException from '../exceptions/HttpException'; +import { Applicant } from '../models/applicant'; +import { Application } from '../models/application'; +import { ApplicationAssignments } from '../models/application_assignments'; +import { ApplicationEvaluation, approvalTypes } from '../models/application_evaluation'; +import { ApplicationEvaluationCompetency } from '../models/application_evaluation_competency'; +import { ApplicantEvaluationFeedback } from '../models/applicant_evaluation_feedback'; +import { Competency } from '../models/competency'; +import { CompetencyEvaluation } from '../models/competency_evaluation'; +import { CompetencySelectors } from '../models/competency_selectors'; +import { Specialty } from '../models/specialty'; +import { SpecialtyCompetencies } from '../models/specialty_competencies'; + +import { logger } from '../utils/logger'; +import { AssessmentHurdle } from '../models/assessment_hurdle'; +import CompetencyService from './competency.service'; +const displayTypes = { + selectorsWithNoJustification: 1, + experience: 2, + selectorsRequiringJustification: 3, +}; +import { CompetencyEvaluationCount } from '../models/competency_evaluation_count'; +export default class EvaluationService { + competencyService = new CompetencyService(); + /** + * + * @param dbInstance + * @param body + * @param assessmentHurdleId + * @param applicantId + * @param evaluatorId + * @returns + * + * This is a bit of a commented out wasteland due to some changing + * requirements. As the tool was originally built to handle _multiple_ + * specialties, there was a lot of logic that went into validating that. + * + * This is no longer the case. + * + * It looks like we should have built in "evaluation_validation_type" + * attached to each "hiring action" to determine how the evaluation would be + * validated. Specifically there are have been evaluations that have ranged + * from: + * + * - only a single failing competency is required + * - all competencies must be selected + * - all competencies must have explanations (except for experience competencies) + * + * and some variations of these. + */ + async submitEvaluation( + dbInstance: DBInterface, + body: EvaluationSubmitDto, + assessmentHurdleId: string, + applicantId: string, + evaluatorId: string, + ): Promise<[ApplicationEvaluation[], CompetencyEvaluation[]]> { + try { + logger.debug(`Submit Evaluation for ${applicantId}`); + // const applicantEvaluationNote = body.note; + + // Used to determine if there needs to be a review at all + let anyEvalNote = body.note; + + // let applicantScreenedOut = false; + const applicant = await Applicant.findByPk(applicantId); + + if (!applicant) throw new HttpException(404, `Applicant not found, applicant id:${applicantId}`); + if (applicant?.assessment_hurdle_id !== assessmentHurdleId) { + logger.error(`Applicant appeared in wrong hurdle${applicant.id}`); + throw new HttpException( + 400, + `Applicant is not part of the assessment hurdle. ApplicantId: ${applicant?.id}; assessmentHurdleId: ${assessmentHurdleId}`, + ); + } + + const allApplications = await Application.findAll({ + where: { applicant_id: applicantId }, + include: [ + { + model: Specialty as any, + required: true, + include: [SpecialtyCompetencies as any], + }, + ], + }); + // logger.debug(`${allApplications.length} applications found for ${applicantId}`); + // There is no longer any screenout OR specialty... + + // const allSpecialties: Specialty[] = allApplications.map(a => a.Specialty); + + // const competencyIdsBySpecialty = allSpecialties.reduce((memo, s) => { + // memo[s.id!] = s.SpecialtyCompetencies.map(sc => sc.competency_id!); + // return memo; + // }, {} as { [specialtyId: string]: string[] }); + + // const uniqueCompetencyIds = Object.values(competencyIdsBySpecialty) + // .flat() + // .filter((v, idx, arr) => arr.indexOf(v) === idx); + + // const allCompetencies = await Competency.findAll({ + // where: { id: uniqueCompetencyIds }, + // attributes: ['id', 'screen_out', 'display_type'], + // }); + // logger.debug(`${allCompetencies.length} competencies for candidate`); + + // const allCompetencyScreenoutStatusById = allCompetencies.reduce((memo, c) => { + // memo[c.id] = { + // screenout: c.screen_out!, + // requireNote: c.display_type == displayTypes.selectorsRequiringJustification, + // }; + // return memo; + // }, {} as { [compId: string]: { screenout: boolean; requireNote: boolean } }); + + const submittedSelectorIds: string[] = body.competencyEvals.map(ce => ce.selectorId).filter(s => s); + + logger.debug(`${submittedSelectorIds.length} Competency Selectors submitted`); + + //get the competency Selectors which contain pass/fail attributes + const evalSelectorIds = await CompetencySelectors.findAll({ + attributes: ['point_value', 'competency_id', 'id'], + where: { + id: submittedSelectorIds, + }, + }); + + const evalNoteBySelectorId = body.competencyEvals.reduce((m, c) => { + anyEvalNote += c.note || ''; + m[c.selectorId] = c.note; + return m; + }, {} as { [compId: string]: string }); + + const evalByCompId = evalSelectorIds.reduce((memo, s) => { + memo[s.competency_id!] = { + selectorId: s.id, + note: evalNoteBySelectorId[s.id], + point_value: s.point_value!, + }; + return memo; + }, {} as { [compId: string]: { selectorId: string; note: string; point_value: number } }); + + // Ensure that all selectors are present and valid + // `where id=submittedSelectorIds & length == length` + logger.debug(`Fetched ${evalSelectorIds.length} CompetencySelectors`); + if (evalSelectorIds.length != submittedSelectorIds.length) { + throw new HttpException(400, 'Competency evaluation selector not found!'); + } + //#region competency validation + + //#region simple comp validation + /** + * Assumes a single implementation where: + * 0. Has correct tie-breaker status + * 1. All _active_ competencies must be reviewed + * 2. All non-experience competencies must have explanations + * 3. Nothing else matters. + * */ + // + const competencyIdsSubmitted = body.competencyEvals.map(ce => ce.competencyId); + + const activeCompetencies = await this.competencyService.getAllActiveForApplicant(evaluatorId, applicantId, assessmentHurdleId); + + // 0. Has correct tie breaker status + const { isTieBreaker } = activeCompetencies; + if (isTieBreaker !== body.isTieBreaker) { + throw new HttpException( + 400, + `There was an error evaluating ${applicant.name}. It’s possible that an evaluation was submitted by another SME that requires your review. To resolve: +1. Please try to continue to the next applicant, or resubmit your review if the same applicant reappears. + +2. If you are unable to review an applicant despite multiple submissions, please flag the applicant with a note about what you observed. + +3. If flagging the applicant does not work, please try refreshing the page. If you are still stuck after refreshing the page, please contact HR.`, + ); + } + + // 1. Check that all active competencies are getting reviewed + const allCompetenciesReviewed = Object.keys(activeCompetencies.competencies).sort().join('') === competencyIdsSubmitted.sort().join(''); + if (!allCompetenciesReviewed) { + throw new HttpException(400, 'Review is incomplete'); + } + // 2. Check that all non-experience competencies have explanations + const competencyById = activeCompetencies.competencies; + body.competencyEvals.forEach(ce => { + const currentDisplayType = competencyById[ce.competencyId].display_type; + switch (true) { + // temporarily have both selectors fail if there is no justification. + case currentDisplayType === displayTypes.selectorsRequiringJustification: + case currentDisplayType === displayTypes.selectorsWithNoJustification: + if (!ce.note || !ce.note.length) { + throw new HttpException(400, `Competency requires a justification: ${competencyById[ce.competencyId].name}`); + } + return; + case currentDisplayType === displayTypes.experience: + default: + return; + } + }); + //#endregion + /* + const applicantReviewedCompetencies = await this.competencyService.getAll; + // Check Competencies 1: Missing Competencies + // if all point_values > 0 then we must have all the CompetencyIds submitted + if (evalSelectorIds.every(ce => ce.point_value! > 0)) { + const allCompetenciesPresent = allCompetencies.every(c => c.id in evalByCompId); + //all the ids returned should equal what we expected + if (!allCompetenciesPresent) { + throw new HttpException(400, `Competency counts off for eval point_value`); + } + } + // Check Competencies 2: Extra competencies + // Check for invalid comp submissions + if (!evalSelectorIds.every(ce => ce.competency_id! in allCompetencyScreenoutStatusById)) { + throw new HttpException(400, `Invalid Extra Competency Data submitted`); + } + + // Validate that we have everything we need for each specialty + /** + * Validate each specialty + * + * Specialty Check 1a: Did applicant fail by screenout? + * Specialty Check 1b: is there a competency note? + * + * Specialty Check 2: are all competencies present? + * Specialty check 3a: Did applicant fail by point value? + * Specialty Check 3b: is there an applicant note? + * + *\/ + Object.entries(competencyIdsBySpecialty).forEach(([specialtyId, competencyIds]) => { + const specialty = allSpecialties.find(s => (s.id = specialtyId))!; + const competencies = competencyIds.map(cid => ({ + id: cid, + screen_out: allCompetencyScreenoutStatusById[cid].screenout, + requireNote: allCompetencyScreenoutStatusById[cid].requireNote, + })); + logger.debug(`Checking Pass/Fail logic for specialty ${specialtyId} with ${competencyIds.length} competencies`); + + // Specialty Check 1a: Did applicant fail by screenout? + const failingEvals = evalSelectorIds.filter(es => es.point_value === 0); + const screenedOut = failingEvals.reduce((screenedOut, es) => { + if (screenedOut) { + return true; + } + if (allCompetencyScreenoutStatusById[es.competency_id!].screenout) { + logger.debug(`Screening out on competency ${es.competency_id} with note '${evalByCompId[es.competency_id!].note}'`); + // Specialty Check 1b: is there a competency note? + + if (allCompetencyScreenoutStatusById[es.competency_id!].requireNote && !evalByCompId[es.competency_id!].note) { + throw new HttpException(400, 'Failing competencies require a note'); + } + // applicantScreenedOut = true; + return true; + } + return false; + }, false); + + if (screenedOut) { + logger.debug(`Screened out of specialty`); + // Valid for this specialty + return; + } + + // Specialty Check 2: are all competencies present? + competencies.forEach(c => { + if (!evalByCompId[c.id].selectorId) { + throw new HttpException(400, 'All competencies require a value'); + } + }); + // Add point values to competencies + const competenciesWithPoints = competencies.map(c => ({ ...c, points: evalByCompId[c.id].point_value })); + + // Specialty check 3a: Did applicant fail by point value? + const totalVal = competenciesWithPoints.reduce((sum, comp) => (sum += comp.points), 0); + logger.debug(`Total Score: ${totalVal}`); + if (totalVal < specialty.points_required!) { + logger.debug(`Score failing with a value of: ${totalVal}/specialty.points_required`); + // Specialty Check 3b: is there an applicant note? + + if (!applicantEvaluationNote) { + throw new HttpException(400, 'Failing specialties require an application note'); + } + } + }); + + //#endregion +*/ + logger.debug(`Creating evaluation results for ${allApplications.length} applications for applicant ${applicantId}`); + //insert results + const appEvals = allApplications.map(a => ({ + application_id: a.id, + evaluation_note: body.note, + evaluator: evaluatorId, + approved: null, + approver_id: null, + feedback_timestamp: new Date(), + })); + + //create a sequelize managed transaction + const [applicationEvals, competencyEvals] = await dbInstance.transaction(async (t: Transaction) => { + const applicationEvals = await Promise.all( + appEvals.map(async ae => (await ApplicationEvaluation.upsert(ae, { transaction: t, returning: true }))[0]), + ); + // const applicationEvals = await ApplicationEvaluation.bulkCreate(appEvals, { + // returning: true, + // transaction: t, + // updateOnDuplicate: ['evaluation_note'], + // }); + logger.debug(`[Transaction] Upserted ${applicationEvals.length} ApplicationEvaluations`); + + const compEvals = Object.entries(evalByCompId).map(([compId, evaluation]) => ({ + competency_id: compId, + applicant: applicantId!, + evaluator: evaluatorId, + evaluation_note: evaluation.note, + competency_selector_id: evaluation.selectorId, + })); + + const competencyEvals = await Promise.all( + compEvals.map(async ce => (await CompetencyEvaluation.upsert(ce, { transaction: t, returning: true }))[0]), + ); + // const competencyEvals = await CompetencyEvaluation.bulkCreate(compEvals, { + // returning: true, + // transaction: t, + // updateOnDuplicate: ['evaluation_note', 'competency_selector_id'], + // }); + logger.debug(`[Transaction] Upserted ${competencyEvals.length} CompetencyEvaluations`); + + const mappings: any[] = []; + applicationEvals.forEach(ae => { + competencyEvals.forEach(ce => { + mappings.push({ application_evaluation_id: ae.id, competency_evaluation_id: ce.id }); + }); + }); + + /** + * This could be a bulkCreate, but for clarity it will match the pattern above. + */ + // const createdMappings = await ApplicationEvaluationCompetency.bulkCreate(mappings, { returning: true, transaction: t, ignoreDuplicates: true }); + + const createdMappings = await Promise.all( + mappings.map(async m => (await ApplicationEvaluationCompetency.upsert(m, { transaction: t, returning: true }))[0]), + ); + + logger.debug( + `[Transaction] Upserted ${createdMappings.length} ApplicationEvaluationCompetency from ${applicationEvals.length} ApplicationEvaluation and ${competencyEvals.length} CompetencyEvaluation`, + ); + + await ApplicationAssignments.upsert( + { active: false, applicant_id: applicantId, evaluator_id: evaluatorId, assessment_hurdle_id: assessmentHurdleId }, + { transaction: t }, + ); + + logger.debug(`[Transaction] Updated evaluator [${evaluatorId}] assignment of ${applicantId} to inactive`); + /** + * Only for Resume review - pass evaluations with no note + */ + if (!anyEvalNote) { + const assessment = await AssessmentHurdle.findByPk(assessmentHurdleId, { + attributes: ['require_review_for_all_passing'], + }); + if (!assessment) { + throw new Error('No assessment found'); + } + // We don't do auto validations anymore I guess. + // if (!assessment.require_review_for_all_passing) { + // const evalutaionIds = applicationEvals.map(a => a.id); + // const [num] = await ApplicationEvaluation.update( + // { + // approved: true, + // approved_type: approvalTypes.automaticApproval, + // }, + // { + // transaction: t, + // where: { id: evalutaionIds }, + // returning: true, + // }, + // ); + // logger.debug(`ApplicationEvaluation auto-validation for ${num} application evaluations.`); + // } + } + return [applicationEvals, competencyEvals]; + }); + logger.debug(`SubmitEvaluation resulted in ${applicationEvals.length} ApplicationEvals and ${competencyEvals.length} CompetencyEvals`); + return [applicationEvals, competencyEvals]; + } catch (err) { + await ApplicationAssignments.upsert({ + active: false, + applicant_id: applicantId, + evaluator_id: evaluatorId, + assessment_hurdle_id: assessmentHurdleId, + }); + throw err; + } + } + + async submitApplicationFeedback( + body: EvaluationApplicationFeedbackDto, + assessmentHurdleId: string, + applicantId: string, + reviewer: string, + ): Promise { + const [rst] = await ApplicantEvaluationFeedback.upsert( + { + id: body.existingId, + applicant_id: applicantId, + evaluation_feedback: body.feedback, + evaluator_id: body.evaluatorId, + feedback_author_id: reviewer, + feedback_timestamp: new Date(), + }, + { + returning: true, + }, + ); + logger.debug(`Upserted ${rst.id} ApplicantEvaluationFeedback`); + + const [ApplicationAssignment] = await ApplicationAssignments.upsert({ + active: true, + applicant_id: applicantId, + evaluator_id: body.evaluatorId, + assessment_hurdle_id: assessmentHurdleId!, + }); + + logger.debug(`Updated ApplicationAssignments ${ApplicationAssignment.id} to active due to feedback`); + + return rst; + } + + async submitApplicationReview( + db: DBInterface, + body: EvaluationApplicationReviewSubmitDto, + reviewerId: string, + assessmentHurdleId: string, + ): Promise { + const assessmentHurdleIds = ( + await Applicant.findAll({ + attributes: ['assessment_hurdle_id'], + include: [ + { + required: true, + model: Application as any, + attributes: ['id'], + + include: [ + { + model: ApplicationEvaluation as any, + required: true, + attributes: ['id'], + where: { + id: body.evaluationId, + }, + }, + ], + }, + ], + }) + ).flatMap(a => a['assessment_hurdle_id']); + + const canUpdate = assessmentHurdleIds.reduce((id: string | boolean, nextId) => { + if (!id) return id; + return id === nextId; + }, assessmentHurdleId as string); + + if (!canUpdate) { + throw new HttpException( + 401, + `Reviewer is unauthorized on applicant set. Reviewer: ${reviewerId} Applicant set: ${JSON.stringify(body.evaluationId)}`, + ); + } + const currentEvals = await ApplicationEvaluation.findAll({ + where: { + id: body.evaluationId, + }, + }); + // You can only update if the current evaluation is either false or there is no evaluation. Unapproved applications must be returned by the SME. + const canBeUpdated = currentEvals + .map(ce => ce.approved) + .reduce((memo, approved) => { + if (!memo) return memo; + if (approved === false) return false; + return true; + }, true); + + if (!canBeUpdated && body.review) { + throw new HttpException(400, `Review was previously invalidated and must be resubmitted by applicant before it can be updated.`); + } + const [num, updated] = await ApplicationEvaluation.update( + { + approved: body.review, + approver_id: reviewerId, + }, + { + where: { id: body.evaluationId }, + returning: true, + }, + ); + await Promise.all(body.evaluationId.map(async id => await db.auditQuery(`SELECT "audit_evaluation"('${id}');`))); + const appEval = updated[0]; + const application = await appEval.getApplication(); + if (!body.review) { + await ApplicationAssignments.upsert({ + active: true, + assessment_hurdle_id: assessmentHurdleId, + evaluator_id: appEval.evaluator!, + applicant_id: application.applicant_id!, + }); + } + logger.debug(`Review Submission updated ${num} ApplicationEvaluation for ${body.evaluationId}`); + return updated[0]; + } +} diff --git a/api/src/services/export.service.ts b/api/src/services/export.service.ts new file mode 100644 index 0000000..4a05994 --- /dev/null +++ b/api/src/services/export.service.ts @@ -0,0 +1,381 @@ +import { Op } from 'sequelize'; +import AuditFileDto from '../dto/auditfile.dto'; +import { Applicant } from '../models/applicant'; +import { ApplicantMeta } from '../models/applicant_meta'; +import { Application } from '../models/application'; +import { ApplicationEvaluation } from '../models/application_evaluation'; +import { ApplicationEvaluationCompetency } from '../models/application_evaluation_competency'; +import { ApplicationMeta } from '../models/application_meta'; +import { AppUser } from '../models/app_user'; +import { CompetencyEvaluation } from '../models/competency_evaluation'; +import { AssessmentHurdle } from '../models/assessment_hurdle'; +import { logger } from '../utils/logger'; +import HttpException from '../exceptions/HttpException'; +import { Competency } from '../models/competency'; +import { CompetencySelectors } from '../models/competency_selectors'; +import { ApplicantStatusMetrics } from '../models/applicant_status_metrics'; +import ApplicationResultDto from '../dto/applicationresult.dto'; +import { AssessmentHurdleMeta } from '../models/assessment_hurdle_meta'; +import ApplicationResultUSASDto from '../dto/applicationresultsusas.dto'; +import { ApplicantRecusals } from '../models/applicant_recusals'; +import RecusedApplicantsDto from '../dto/recusedapplicants.dto'; +import FlaggedApplicantsDto from '../dto/flaggedapplicants.dto'; + +export default class ExportService { + async getAuditFile(hurdleId: string): Promise { + logger.info(`Generating Export Audit file for ${hurdleId}`); + + const countCheck = await AssessmentHurdle.count({ + where: { + id: hurdleId, + }, + }); + + if (countCheck === 0) throw new HttpException(404, `${hurdleId} not found`); + + const applicants = await Applicant.findAll({ + where: { + assessment_hurdle_id: hurdleId, + }, + include: [ + { + model: ApplicantMeta as any, + required: false, + }, + { + model: Application as any, + required: true, + include: [ApplicationMeta as any], + }, + ], + }); + + logger.info(`${applicants.length} applicants found`); + + const applicationsScope = applicants.flatMap(a => a.Applications); + + logger.info(`${applicationsScope.length} applications found`); + + const appIds = applicationsScope.map(appScope => appScope.id); + + const applicationEvals = await ApplicationEvaluation.findAll({ + where: { + application_id: { + [Op.in]: appIds, + }, + }, + include: [ + { + model: ApplicationEvaluationCompetency as any, + required: true, + }, + { + model: Application as any, + required: true, + }, + { + model: AppUser as any, + required: true, + as: 'Evaluator', + attributes: ['id', 'name', 'email'], + }, + { + model: AppUser as any, + required: false, + as: 'Approver', + attributes: ['id', 'name', 'email'], + }, + ], + }); + + const compEvalsToFetch = applicationEvals.flatMap(ae => ae.ApplicationEvaluationCompetencies).map(aec => aec.competency_evaluation_id!); + + const compEvals = await CompetencyEvaluation.findAll({ + where: { + id: { + [Op.in]: compEvalsToFetch, + }, + }, + include: [ + { + model: Competency as any, + required: true, + }, + { + model: CompetencySelectors as any, + required: true, + }, + ], + }); + + logger.info(`${applicationEvals.length} applicationEvalutions found`); + logger.info(`${compEvals.length} CompetencyEvaluation found`); + + const mappingItems: ApplicationEvaluationCompetency[] = applicationEvals.flatMap(appEvals => appEvals.ApplicationEvaluationCompetencies); + logger.info(`${mappingItems.length} ApplicationEvaluationCompetencies found`); + + const competencies = await Competency.findAll({ + where: { + assessment_hurdle_id: hurdleId, + }, + attributes: ['id', 'name'], + }); + const resultsByAppNumber = (await this.getUSASResult(hurdleId)).reduce((memo, result) => { + memo[result.applicationNumber] = result; + return memo; + }, {} as { [usasAppNumber: string]: ApplicationResultUSASDto }); + const mapped: AuditFileDto[] = []; + + applicationEvals.forEach(appEval => { + const matchedMappings = mappingItems.filter(mi => mi.application_evaluation_id! === appEval.id).map(mm => mm.competency_evaluation_id); + const matchedComp = compEvals.filter(ce => matchedMappings.includes(ce.id)).sort((a, b) => a.Competency.name.localeCompare(b.Competency.name)); + const compObject = matchedComp.reduce((memo, c) => { + memo[c.competency_id] = c; + return memo; + }, {} as { [compId: string]: CompetencyEvaluation }); + + const applicant = applicants.filter(a => a.id === appEval.Application.applicant_id!)[0]; + + const dto = new AuditFileDto(); + dto.assessmentHurdleId = hurdleId; + dto.applicationId = appEval.id; + dto.applicantId = applicant.id; + dto.applicantName = applicant.name!; + dto.specialtyId = appEval.Application.specialty_id!; + dto.flagType = applicant.flag_type!; + if (appEval.Approver) { + dto.approverEmail = appEval.Approver.email; + dto.approved = appEval.approved!; + } + dto.timestamp = appEval.created_at!.toISOString(); + + dto.evaluatorEmail = appEval.Evaluator.email; + dto.evaluatorNote = appEval.evaluation_note!; + dto.result = resultsByAppNumber[applicant.ApplicantMetum.staffing_application_number!].minQualificationsRating; + + competencies.forEach(comp => { + dto[comp.name! + ' Result'] = compObject[comp.id]?.CompetencySelector?.display_name || ''; + dto[comp.name + ' Justification'] = compObject[comp.id]?.evaluation_note || ''; + }); + mapped.push(dto); + }); + + logger.info(`Generated ${mapped.length} audit records`); + return mapped.sort((a, b) => (a.applicantId > b.applicantId ? 1 : -1)); + } + + async getGeneralResult(hurdleId: string): Promise { + logger.info(`Generating General file for ${hurdleId}`); + + const countCheck = await AssessmentHurdle.count({ + where: { + id: hurdleId, + }, + }); + + if (countCheck === 0) throw new HttpException(404, `${hurdleId} not found`); + + const allApplicants = await Applicant.findAll({ + where: { + assessment_hurdle_id: hurdleId, + }, + }); + const applicantIds = allApplicants.map(aa => aa.id); + logger.info(`${applicantIds.length} applicants found`); + + const aggResults = await ApplicantStatusMetrics.findAll({ + where: { + applicant_id: { + [Op.in]: applicantIds, + }, + }, + }); + + logger.info(`${aggResults.length} ApplicantStatusMetrics found`); + + // const getFinalScore = (ar: ApplicantStatusMetrics): string => { + // if (ar.does_not_meet! >= ar.evaluations_required!) return 'does not meet'; + // if (ar.exceeds! >= ar.evaluations_required!) return 'exceeds'; + // if (ar.meets! + ar.exceeds! >= ar.evaluations_required!) return 'meets'; + // return 'insuffient evaluations'; + // }; + + const mapped: ApplicationResultDto[] = aggResults.map(ar => { + const applicant = allApplicants.find(aa => aa.id === ar.applicant_id!)!; + return new ApplicationResultDto({ + applicantId: ar.applicant_id, + applicantName: applicant.name!, + assessmentHurdleId: hurdleId, + // finalScore: getFinalScore(ar), + }); + }); + + return mapped; + } + + async getUSASResult(hurdleId: string): Promise { + logger.info(`Generating USAS file for ${hurdleId}`); + + const assessmentHurdle = await AssessmentHurdle.findByPk(hurdleId, { + include: [ + { + model: AssessmentHurdleMeta as any, + required: true, + }, + ], + }); + + if (assessmentHurdle === null) throw new HttpException(404, `${hurdleId} not found`); + + const allApplicants = await Applicant.findAll({ + where: { + assessment_hurdle_id: hurdleId, + }, + include: [ + { + model: ApplicantMeta as any, + required: true, + }, + ], + }); + + const applicantIds = allApplicants.map(aa => aa.id); + logger.info(`${applicantIds.length} applicants found`); + const { hurdle_display_type } = assessmentHurdle; + const { staffing_fail_nor, staffing_pass_nor } = assessmentHurdle.AssessmentHurdleMetum; + const getNORCode = (ar: ApplicantStatusMetrics): string => { + if (hurdle_display_type !== 1) return ''; + switch (ar.review_status!) { + case 'pending amendment': + return 'PENDING_AMENDMENT'; + case 'pending review': + return 'PENDING_REVIEW'; + case 'pending evaluations': + return 'PENDING_EVALUATIONS'; + default: + break; + } + switch (ar.evaluation_status!) { + case 'does_not_meet': + return staffing_fail_nor || 'DOES_NOT_PASS'; + case 'meets': + return staffing_pass_nor || 'PASS'; + case 'error': + return 'ERROR'; + default: + return 'ERROR'; + } + }; + + const getRatingScore = (ar: ApplicantStatusMetrics): number | null => { + if (assessmentHurdle.hurdle_display_type === 1) return null; + else throw new HttpException(500, 'rating type not implemented'); + }; + + const aggResults = await ApplicantStatusMetrics.findAll({ + where: { + applicant_id: { + [Op.in]: applicantIds, + }, + }, + include: [ + { + model: Applicant as any, + required: true, + attributes: ['id', 'assessment_hurdle_id'], + include: [ + { + model: ApplicantMeta as any, + required: true, + }, + { + model: Application as any, + required: true, + separate: true, + include: [ + { + model: ApplicationMeta as any, + required: true, + }, + ], + }, + ], + }, + ], + }); + logger.info(`${aggResults.length} ApplicantStatusMetrics found`); + + const mapped: ApplicationResultUSASDto[] = aggResults.map(ar => { + return new ApplicationResultUSASDto({ + vacancyId: assessmentHurdle.AssessmentHurdleMetum.staffing_vacancy_id, + assessmentId: assessmentHurdle.AssessmentHurdleMetum.staffing_assessment_id, + applicationId: ar.Applicant.ApplicantMetum.staffing_application_id, + applicationNumber: ar.Applicant.ApplicantMetum.staffing_application_number, + applicationRatingId: ar.Applicant.Applications[0].ApplicationMetum.staffing_application_rating_id!, + applicantFirstName: ar.Applicant.ApplicantMetum.staffing_first_name!, + applicantLastName: ar.Applicant.ApplicantMetum.staffing_last_name!, + applicantMiddleName: ar.Applicant.ApplicantMetum.staffing_middle_name!, + ratingCombination: ar.Applicant.Applications[0].ApplicationMetum.staffing_rating_combination!, + minQualificationsRating: getNORCode(ar), + assessmentRating: getRatingScore(ar), + }); + }); + + return mapped; + } + + async getRecusedApplicants(hurdleId: string): Promise { + logger.info(`Generating RecusedApplicants file for ${hurdleId}`); + + const assessmentHurdle = await AssessmentHurdle.findByPk(hurdleId, { + include: [ + { + model: AssessmentHurdleMeta as any, + required: true, + }, + ], + }); + + if (assessmentHurdle === null) throw new HttpException(404, `${hurdleId} not found`); + + const allApplicants = await Applicant.findAll({ + where: { + assessment_hurdle_id: hurdleId, + }, + }); + const allRecusals = await ApplicantRecusals.findAll({ + where: { + applicant_id: allApplicants.map(a => a.id), + }, + include: [ + { + model: Applicant as any, + required: true, + include: [ + { + model: ApplicantMeta as any, + required: true, + }, + ], + }, + { model: AppUser as any, required: true }, + ], + }); + + const mapped: AuditFileDto[] = allRecusals.map(aa => { + const dto = new AuditFileDto(); + dto.applicantId = aa.Applicant.id; + dto.assessmentHurdleId = hurdleId; + dto.applicantName = `${aa.Applicant.ApplicantMetum.staffing_first_name} ${aa.Applicant.ApplicantMetum.staffing_middle_name} ${aa.Applicant.ApplicantMetum.staffing_last_name}`; + dto.evaluatorEmail = aa.AppUser.email; + dto.evaluatorNote = 'Recused from applicant'; + dto.competencyEvaluations = 'Recused from applicant'; + dto.approverEmail = 'NA'; + dto.approved = true; + dto.timestamp = aa.created_at!.toISOString(); + return dto; + }); + + return mapped; + } +} diff --git a/api/src/services/metrics.service.ts b/api/src/services/metrics.service.ts new file mode 100644 index 0000000..3961e6c --- /dev/null +++ b/api/src/services/metrics.service.ts @@ -0,0 +1,155 @@ +import { Op } from 'sequelize'; + +import { logger } from '../utils/logger'; + +import { Applicant } from '../models/applicant'; +import { ApplicantStatusMetrics } from '../models/applicant_status_metrics'; +import { AssessmentHurdle } from '../models/assessment_hurdle'; +import { AssessmentHurdleUser } from '../models/assessment_hurdle_user'; +import { ApplicantRecusals, ApplicationAssignments, ApplicationEvaluationAgg, AppUser, ReviewerMetrics } from '../models/init-models'; +import { EvaluatorMetrics } from '../models/evaluator_metrics'; +import sequelize from 'sequelize'; + +type ApplicantNameKey = { [applicantId: string]: string }; +type AppUserNameKey = { [appUserId: string]: { name: string; email: string } }; +export default class MetricsService { + private async _getApplicantNames(hurdleId: string): Promise { + const applicants = await Applicant.findAll({ + where: { + assessment_hurdle_id: hurdleId, + }, + }); + + return applicants.reduce((memo, a) => { + memo[a.id] = a.name!; + return memo; + }, {} as ApplicantNameKey); + } + private async _getUserNames(hurdleId: string): Promise { + const assessmentHurdleEvaluators = await AssessmentHurdleUser.findAll({ + where: { + assessment_hurdle_id: hurdleId, + }, + attributes: [], + include: [ + { + model: AppUser as any, + required: true, + attributes: ['id', 'email', 'name'], + }, + ], + }); + return assessmentHurdleEvaluators + .flatMap(e => e.AppUser) + .reduce((memo, appuser) => { + memo[appuser.id] = { name: appuser.name!, email: appuser.email! }; + return memo; + }, {} as AppUserNameKey); + } + async getOverallMetrics(hurdleId: string) { + const applicantNames = await this._getApplicantNames(hurdleId); + const appUserNames = await this._getUserNames(hurdleId); + + const applicants = await ApplicantStatusMetrics.findAll({ + where: { + assessment_hurdle_id: hurdleId, + }, + }); + const evaluators = await EvaluatorMetrics.findAll({ + where: { + assessment_hurdle_id: hurdleId, + evaluator: { [Op.ne]: null }, + }, + }); + + const reviewers = await ReviewerMetrics.findAll({ + where: { + assessment_hurdle_id: hurdleId, + }, + }); + + const assignments = await this.getCurrentAssignments(hurdleId, appUserNames, applicantNames); + + return { + applicants, + evaluators, + reviewers, + assignments, + }; + } + + async getCurrentAssignments(hurdleId: string, evaluators: AppUserNameKey, applicants: ApplicantNameKey) { + const assingments = await ApplicationAssignments.findAll({ + where: { assessment_hurdle_id: hurdleId, active: true }, + attributes: ['id', 'evaluator_id', 'applicant_id', 'updated_at'], + }); + + return assingments.map(a => { + const { evaluator_id, applicant_id, updated_at, id } = a; + return { + id, + evaluator: evaluators[evaluator_id].name, + applicant: applicants[applicant_id], + updated_at: new Date(updated_at!).toLocaleString(), + }; + }); + } + + async getReviewerMetrics(hurdleId: string) { + logger.debug(`Generating getReviewerMetrics for hurdle ${hurdleId}`); + + const reviewerMetrics = await ReviewerMetrics.findAll({ + where: { + assessment_hurdle_id: hurdleId, + }, + }); + + return reviewerMetrics.map(rm => { + const reviewerId = rm.reviewer_id!; + return { + id: reviewerId, + name: rm.name!, + email: rm.email!, + pending_amendment: rm.pending_amendment!, + adjudicated: rm.adjudicated!, + }; + }); + } + + async getHiringActionAggregates(hurdleId: string) { + const { evaluations_required } = (await AssessmentHurdle.findByPk(hurdleId, { + attributes: ['evaluations_required'], + }))!; + const totalApplicants = await Applicant.count({ + where: { + assessment_hurdle_id: hurdleId, + }, + }); + const applicantMetrics = await ApplicantStatusMetrics.findAll({ + where: { + assessment_hurdle_id: hurdleId, + }, + }); + const totalEvaluationsNeeded = Math.floor(evaluations_required * totalApplicants * 1.2); + + const totalEvaluations = applicantMetrics.reduce((memo, { evaluators }) => { + memo += +evaluators!; + return memo; + }, 0); + + return { + totalEvaluations, + totalEvaluationsNeeded, + totalApplicants, + }; + } + async getEvaluatorTotals(assessment_hurdle_id: string, evaluator: string) { + const evaluatorMetrics = await EvaluatorMetrics.findOne({ + where: { + assessment_hurdle_id, + evaluator, + }, + }); + return evaluatorMetrics; + } +} diff --git a/api/src/services/review.service.ts b/api/src/services/review.service.ts new file mode 100644 index 0000000..93354de --- /dev/null +++ b/api/src/services/review.service.ts @@ -0,0 +1,45 @@ +import { Applicant } from '../models/applicant'; + +import { logger } from '../utils/logger'; + +export default class ReviewService { + async submitApplicantFeedback(assessmentHurdleId: string, applicantId: string, reviewer: string, feedback: string): Promise { + await Applicant.update( + { + additional_note: feedback, + }, + { + where: { + id: applicantId, + assessment_hurdle_id: assessmentHurdleId, + }, + returning: true, + }, + ); + logger.debug(`Updated applicant note for ${applicantId} from ${reviewer}`); + return applicantId; + } + async updateApplicantFlagStatus( + assessmentHurdleId: string, + applicantId: string, + reviewer: string, + flagStatus: number, + flagMessage = '', + ): Promise { + await Applicant.update( + { + flag_type: flagStatus, + flag_message: flagMessage, + }, + { + where: { + id: applicantId, + assessment_hurdle_id: assessmentHurdleId, + }, + returning: true, + }, + ); + logger.debug(`Updated flag status for ${applicantId} from ${reviewer}. Flag status ${flagStatus}`); + return applicantId; + } +} diff --git a/api/src/services/specialty.service.ts b/api/src/services/specialty.service.ts new file mode 100644 index 0000000..4e2a80e --- /dev/null +++ b/api/src/services/specialty.service.ts @@ -0,0 +1,122 @@ +import CreateSpecialtyDto from '../dto/createspecialty.dto'; +import CreateSpecialtiesDto from '../dto/createspecialties.dto'; +import HttpException from '../exceptions/HttpException'; +import { SpecialtyCompetencies } from '../models/specialty_competencies'; +import { Specialty } from '../models/specialty'; +import { isEmpty } from '../utils/isEmpty'; +import { logger } from '../utils/logger'; +import CompetencyService from './competency.service'; +import { Competency } from '../models/competency'; + +export default class SpecialtyService { + CompetencyService = new CompetencyService(); + + async getById(id: string): Promise { + if (isEmpty(id)) throw new HttpException(400, 'Applicant or specialty not found'); + const rst = await Specialty.findByPk(id); + if (!rst) throw new HttpException(404, 'Not Found'); + return rst; + } + + async getAll(): Promise { + const rst: Specialty[] = await Specialty.findAll(); + return rst; + } + async getAllByHurdleId(hurdleId: string): Promise { + const rst: Specialty[] = await Specialty.findAll({ + where: { + assessment_hurdle_id: hurdleId, + }, + }); + return rst; + } + + async getAllMappingsById(id: string, hurdleId: string): Promise { + if (isEmpty(id)) throw new HttpException(400, 'Applicant or specialty not found'); + const existing = await Specialty.count({ where: { id: id, assessment_hurdle_id: hurdleId } }); + if (existing === 0) { + throw new HttpException(400, `SpecialtyId ${id} does not exist in ${hurdleId}`); + } + const rst: SpecialtyCompetencies[] = await SpecialtyCompetencies.findAll({ where: { specialty_id: id }, include: { all: true } }); + return rst; + } + + async upsert(body: CreateSpecialtyDto, competencies?: Competency[]): Promise { + if (body.competencyIds && body.competencyLocalIds) { + throw new Error('Must provide only either specialyIds or competency local Ids'); + } + + const [instance] = await Specialty.upsert({ + id: body.existingId, + local_id: body.localId, + name: body.name, + assessment_hurdle_id: body.assessmentHurdleId, + points_required: body.pointsRequired, + }); + + logger.debug(`Upserted Specialty ${instance.id}`); + if (body.competencyIds?.length) { + const mapped: SpecialtyCompetencies[] = await Promise.all( + body.competencyIds.map(async compId => { + const [cs, created] = await SpecialtyCompetencies.findOrCreate({ + where: { specialty_id: instance.id, competency_id: compId }, + defaults: { + competency_id: compId, + specialty_id: instance.id, + }, + }); + if (!created) { + cs.update({ + competency_id: compId, + specialty_id: instance.id, + }); + } + return cs; + }), + ); + logger.debug(`Created ${mapped.length} mappings from ${body.competencyIds?.length} ids`); + } else if (body.competencyLocalIds?.length) { + if (!competencies || !competencies.length) { + competencies = await this.CompetencyService.getAllByHurdleId(body.assessmentHurdleId!); + if (!competencies || !competencies.length) { + throw new Error('No competencies exist for this assessment hurdle!'); + } + } + const competencyByLocalId = competencies.reduce((memo, comp) => { + memo[comp.local_id] = comp.id; + return memo; + }, {} as { [local_id: string]: string }); + + const mapped: SpecialtyCompetencies[] = await Promise.all( + body.competencyLocalIds.map(async id => { + const compId = competencyByLocalId[id]; + if (!compId) { + throw new Error(`Competency local id ${id} does not exist`); + } + const [cs, created] = await SpecialtyCompetencies.findOrCreate({ + where: { specialty_id: instance.id, competency_id: compId }, + defaults: { + competency_id: compId, + specialty_id: instance.id, + }, + }); + if (!created) { + cs.update({ + competency_id: compId, + specialty_id: instance.id, + }); + } + return cs; + }), + ); + logger.debug(`Created ${mapped.length} mappings from ${body.competencyIds?.length} ids`); + } + + return instance; + } + + async upsertAll(body: CreateSpecialtiesDto, assessmentHurdleId: string): Promise { + const competencies = await this.CompetencyService.getAllByHurdleId(assessmentHurdleId); + return await Promise.all(body.specialties.map(specialty => this.upsert({ ...specialty, assessmentHurdleId }, competencies))); + } +} diff --git a/api/src/services/users.service.ts b/api/src/services/users.service.ts new file mode 100644 index 0000000..5e25812 --- /dev/null +++ b/api/src/services/users.service.ts @@ -0,0 +1,83 @@ +import CreateHurdleUserDto from '../dto/createhurdleuser.dto'; +import CreateUserDto from '../dto/createuser.dto'; +import HttpException from '../exceptions/HttpException'; +import { AppUser } from '../models/app_user'; +import { AssessmentHurdleUser } from '../models/assessment_hurdle_user'; +import { logger } from '../utils/logger'; + +export default class UserService { + async getAllUsersByHurdle(assessmentHurdle: string): Promise { + const rst = await AssessmentHurdleUser.findAll({ + where: { assessment_hurdle_id: assessmentHurdle }, + include: [{ model: AppUser as any, required: true }], + }); + return rst; + } + async getAllUsers(): Promise { + const rst: AppUser[] = await AppUser.findAll(); + return rst; + } + + async getUserById(id: string): Promise { + const findUser = await AppUser.findByPk(id); + if (!findUser) throw new HttpException(404, 'Not Found'); + return findUser; + } + + async getUserByEmail(email: string): Promise { + const findUser = await AppUser.findOne({ where: { email: email.toLowerCase() } }); + if (!findUser) throw new HttpException(404, 'Not Found'); + return findUser; + } + + async createUser(newUser: CreateUserDto): Promise { + const createUser = await AppUser.create({ + email: newUser.email.toLowerCase(), + name: newUser.name, + }); + return createUser; + } + + async createUserAndAddToHurdle(container: CreateHurdleUserDto, assessmentHurdleId: string): Promise { + logger.debug(`createUserAndAddToHurdle for ${container.userSetup.length} userSetup pairs`); + const allResults = await Promise.all( + container.userSetup.flatMap(async body => { + const userRecords: AppUser[] = await Promise.all( + body.users.map(async u => { + const [rst] = await AppUser.findOrCreate({ + where: { email: u.email.toLowerCase() }, + defaults: { + email: u.email.toLowerCase(), + name: u.name, + }, + }); + return rst; + }), + ); + const mapped = await Promise.all( + userRecords.map(async ur => { + const [rst, created] = await AssessmentHurdleUser.findOrCreate({ + where: { app_user_id: ur.id, assessment_hurdle_id: assessmentHurdleId }, + defaults: { + app_user_id: ur.id, + assessment_hurdle_id: assessmentHurdleId!, + role: body.role, + }, + }); + if (!created) { + rst.update({ + app_user_id: ur.id, + assessment_hurdle_id: assessmentHurdleId!, + role: body.role, + }); + } + return rst; + }), + ); + return mapped; + }), + ); + //this shouldn't be needed + return allResults.flat(); + } +} diff --git a/api/src/tests/integration.tests/assignments.int.spec.ts b/api/src/tests/integration.tests/assignments.int.spec.ts new file mode 100644 index 0000000..ff544b7 --- /dev/null +++ b/api/src/tests/integration.tests/assignments.int.spec.ts @@ -0,0 +1,168 @@ +import path from 'path'; +import App from '../../app'; +import AssessmentHurdleRoute from '../../routes/assessmenthurdle.routes'; +import EvaluationRoute from '../../routes/evaluation.routes'; +import IntegrationLoader from './util/integrationloader'; +import st, { SuperTest, Test } from 'supertest'; +import { ApplicationAssignments } from '../../models/application_assignments'; + +import EvalSubmission from './util/evaluationSubmission'; + +const integrationLoader = new IntegrationLoader(path.join(__dirname, './data/demoResumeSingleGrade')); +const assessmentRoute = new AssessmentHurdleRoute(); +const evaluationRoute = new EvaluationRoute(); +const application: App = new App(9999, 'testing', [assessmentRoute, evaluationRoute]); + +/** + * THESE TESTS ARE DESIGNED TO RUN SERIALLY + */ + +describe('Basic assignment queue testing', () => { + let request: SuperTest; + let assessmentHurdleId: string; + beforeAll(async done => { + try { + await application.serverReady; + const testingData = await integrationLoader.loadTestingData(true, false); + assessmentHurdleId = testingData.assessmentHurdleId; + request = st(application!.getServer()); + done(); + } catch (err) { + done(err); + } + }); + + it('Assignment returns an applicant', async done => { + const { status, body } = await request + .get(`${assessmentRoute.fullBasePath}/${assessmentHurdleId}/next`) + .set('Authorization', 'bearer evaluator_two') + .send(); + + expect(status).toEqual(200); + const appAssignment = body.data as ApplicationAssignments; + expect(appAssignment.applicant_id).toBeDefined(); + expect(appAssignment.assessment_hurdle_id).toEqual(assessmentHurdleId); + done(); + }); + + it('Multiple calls return the same applicant', async done => { + let firstAssignment: ApplicationAssignments | null = null; + { + const { status, body } = await request + .get(`${assessmentRoute.fullBasePath}/${assessmentHurdleId}/next`) + .set('Authorization', 'bearer evaluator_two') + .send(); + + expect(status).toEqual(200); + const appAssignment = body.data as ApplicationAssignments; + firstAssignment = appAssignment; + } + { + const { status, body } = await request + .get(`${assessmentRoute.fullBasePath}/${assessmentHurdleId}/next`) + .set('Authorization', 'bearer evaluator_two') + .send(); + expect(status).toEqual(200); + const appAssignment = body.data as ApplicationAssignments; + expect(appAssignment.applicant_id).toEqual(firstAssignment!.applicant_id); + done(); + } + }); + + it('Evaluation should remove applicant from queue', async done => { + const evaluator = 'evaluator_two'; + const evalApplicant = new EvalSubmission(application); + let firstAssignment: ApplicationAssignments | null = null; + { + const { status, body } = await request + .get(`${assessmentRoute.fullBasePath}/${assessmentHurdleId}/next`) + .set('Authorization', `bearer ${evaluator}`) + .send(); + expect(status).toEqual(200); + + const appAssignment = body.data as ApplicationAssignments; + firstAssignment = appAssignment; + } + let secondAssignment: ApplicationAssignments | null = null; + { + await evalApplicant.evaluatePassing(assessmentHurdleId, firstAssignment!.applicant_id, evaluator, 'Application Note'); + + const { body } = await request + .get(`${assessmentRoute.fullBasePath}/${assessmentHurdleId}/next`) + .set('Authorization', `bearer ${evaluator}`) + .send(); + const appAssignment = body.data as ApplicationAssignments; + secondAssignment = appAssignment; + expect(appAssignment.applicant_id).not.toEqual(firstAssignment!.applicant_id); + } + { + await evalApplicant.evaluateFailing(assessmentHurdleId, secondAssignment!.applicant_id, evaluator, 'This is an applicant failure note'); + + const { body } = await request + .get(`${assessmentRoute.fullBasePath}/${assessmentHurdleId}/next`) + .set('Authorization', `bearer ${evaluator}`) + .send(); + const appAssignment = body.data as ApplicationAssignments; + expect(appAssignment.applicant_id).not.toEqual(secondAssignment!.applicant_id); + } + done(); + }); + + it('Flagged applicant should be removed from this applicants queue', async done => { + let firstAssignment: ApplicationAssignments | null = null; + const evaluator = 'evaluator_two'; + const evalApplicant = new EvalSubmission(application); + { + const { body, status } = await request + .get(`${assessmentRoute.fullBasePath}/${assessmentHurdleId}/next`) + .set('Authorization', `bearer ${evaluator}`) + .send(); + const appAssignment = body.data as ApplicationAssignments; + firstAssignment = appAssignment; + } + { + const { body, status } = await evalApplicant.flagApplicant(assessmentHurdleId, firstAssignment!.applicant_id, evaluator, 'This is a flag note'); + } + { + const { status, body } = await request + .get(`${assessmentRoute.fullBasePath}/${assessmentHurdleId}/next`) + .set('Authorization', `bearer ${evaluator}`) + .send(); + expect(status).toEqual(200); + const appAssignment = body.data as ApplicationAssignments; + expect(appAssignment.applicant_id).not.toBe(firstAssignment!.applicant_id); + done(); + } + }); + it('Recused applicant should be removed from this applicants queue', async done => { + let firstAssignment: ApplicationAssignments | null = null; + const evaluator = 'evaluator_two'; + const evalApplicant = new EvalSubmission(application); + { + const { body, status } = await request + .get(`${assessmentRoute.fullBasePath}/${assessmentHurdleId}/next`) + .set('Authorization', `bearer ${evaluator}`) + .send(); + const appAssignment = body.data as ApplicationAssignments; + firstAssignment = appAssignment; + } + { + const { body, status } = await evalApplicant.recuseApplicant( + assessmentHurdleId, + firstAssignment!.applicant_id, + evaluator, + 'This is a recusal note', + ); + } + { + const { status, body } = await request + .get(`${assessmentRoute.fullBasePath}/${assessmentHurdleId}/next`) + .set('Authorization', `bearer ${evaluator}`) + .send(); + expect(status).toEqual(200); + const appAssignment = body.data as ApplicationAssignments; + expect(appAssignment.applicant_id).not.toBe(firstAssignment!.applicant_id); + done(); + } + }); +}); diff --git a/api/src/tests/integration.tests/assignmentsMultiple.int.spec.ts b/api/src/tests/integration.tests/assignmentsMultiple.int.spec.ts new file mode 100644 index 0000000..9a22f06 --- /dev/null +++ b/api/src/tests/integration.tests/assignmentsMultiple.int.spec.ts @@ -0,0 +1,208 @@ +import path from 'path'; +import App from '../../app'; +import AssessmentHurdleRoute from '../../routes/assessmenthurdle.routes'; +import EvaluationRoute from '../../routes/evaluation.routes'; +import st, { SuperTest, Test } from 'supertest'; +import { ApplicationAssignments } from '../../models/application_assignments'; + +import IntegrationLoader from './util/integrationloader'; +import EvalSubmission from './util/evaluationSubmission'; + +const integrationLoader = new IntegrationLoader(path.join(__dirname, './data/demoResumeSingleGrade')); +const assessmentRoute = new AssessmentHurdleRoute(); +const evaluationRoute = new EvaluationRoute(); +const application: App = new App(9999, 'testing', [assessmentRoute, evaluationRoute]); + +/** + * THESE TESTS ARE DESIGNED TO RUN SERIALLY + */ + +describe('Assignments requests where multiple evaluators are affected', () => { + let assessmentHurdleId: string; + let request: SuperTest; + beforeAll(async done => { + try { + await application.serverReady; + request = st(application!.getServer()); + done(); + } catch (err) { + done(err); + } + }); + beforeEach(async done => { + try { + await application.serverReady; + const testingData = await integrationLoader.loadTestingData(true, true); + assessmentHurdleId = testingData.assessmentHurdleId; + done(); + } catch (err) { + done(err); + } + }); + it('Different evaluators should get different applicants at queue start', async done => { + let evalOnefirstAssignment: ApplicationAssignments | null = null; + const evaluatorOne = 'evaluator_one'; + { + const { body, status } = await request + .get(`${assessmentRoute.fullBasePath}/${assessmentHurdleId}/next`) + .set('Authorization', `bearer ${evaluatorOne}`) + .send(); + const appAssignment = body.data as ApplicationAssignments; + evalOnefirstAssignment = appAssignment; + } + let evalTwofirstAssignment: ApplicationAssignments | null = null; + const evaluatorTwo = 'evaluator_two'; + { + const { body, status } = await request + .get(`${assessmentRoute.fullBasePath}/${assessmentHurdleId}/next`) + .set('Authorization', `bearer ${evaluatorTwo}`) + .send(); + const appAssignment = body.data as ApplicationAssignments; + evalTwofirstAssignment = appAssignment; + } + let evalThreefirstAssignment: ApplicationAssignments | null = null; + const evaluatorThree = 'evaluator_three'; + { + const { body, status } = await request + .get(`${assessmentRoute.fullBasePath}/${assessmentHurdleId}/next`) + .set('Authorization', `bearer ${evaluatorThree}`) + .send(); + const appAssignment = body.data as ApplicationAssignments; + evalThreefirstAssignment = appAssignment; + } + const ids = [evalOnefirstAssignment.applicant_id, evalTwofirstAssignment.applicant_id, evalThreefirstAssignment.applicant_id]; + const isArrayUnique = (arr: string[]) => Array.isArray(arr) && new Set(arr).size === arr.length; + // expect(1).toBe(1); + expect(isArrayUnique(ids)).toBeTruthy(); + return done(); + }); + it('Evaluating an applicant should make another evaluator prefer that applicant while removing it from the current evaluator queue', async done => { + const evalApplicant = new EvalSubmission(application); + + let evalOnefirstAssignment: ApplicationAssignments | null = null; + const evaluatorOne = 'evaluator_one'; + { + const { body, status } = await request + .get(`${assessmentRoute.fullBasePath}/${assessmentHurdleId}/next`) + .set('Authorization', `bearer ${evaluatorOne}`) + .send(); + const appAssignment = body.data as ApplicationAssignments; + evalOnefirstAssignment = appAssignment; + await evalApplicant.evaluateFailing( + assessmentHurdleId, + evalOnefirstAssignment!.applicant_id, + evaluatorOne, + 'This is an applicant failure note', + ); + } + + let evalTwofirstAssignment: ApplicationAssignments | null = null; + const evaluatorTwo = 'evaluator_two'; + { + const { body, status } = await request + .get(`${assessmentRoute.fullBasePath}/${assessmentHurdleId}/next`) + .set('Authorization', `bearer ${evaluatorTwo}`) + .send(); + const appAssignment = body.data as ApplicationAssignments; + evalTwofirstAssignment = appAssignment; + } + expect(evalOnefirstAssignment.applicant_id).toEqual(evalTwofirstAssignment.applicant_id); + + let evalThreefirstAssignment: ApplicationAssignments | null = null; + const evaluatorThree = 'evaluator_three'; + { + const { body, status } = await request + .get(`${assessmentRoute.fullBasePath}/${assessmentHurdleId}/next`) + .set('Authorization', `bearer ${evaluatorThree}`) + .send(); + const appAssignment = body.data as ApplicationAssignments; + evalThreefirstAssignment = appAssignment; + } + // but evaluator three shouldn't get this applicant + expect(evalThreefirstAssignment.applicant_id).not.toEqual(evalTwofirstAssignment.applicant_id); + done(); + }); + it('Recusing an applicant should just remove the current evaluator, and make it the preference of the next evaluator', async done => { + const evalApplicant = new EvalSubmission(application); + + let evalOnefirstAssignment: ApplicationAssignments | null = null; + const evaluatorOne = 'evaluator_one'; + { + const { body, status } = await request + .get(`${assessmentRoute.fullBasePath}/${assessmentHurdleId}/next`) + .set('Authorization', `bearer ${evaluatorOne}`) + .send(); + const appAssignment = body.data as ApplicationAssignments; + evalOnefirstAssignment = appAssignment; + await evalApplicant.recuseApplicant(assessmentHurdleId, evalOnefirstAssignment!.applicant_id, evaluatorOne); + } + + let evalTwofirstAssignment: ApplicationAssignments | null = null; + const evaluatorTwo = 'evaluator_two'; + { + const { body, status } = await request + .get(`${assessmentRoute.fullBasePath}/${assessmentHurdleId}/next`) + .set('Authorization', `bearer ${evaluatorTwo}`) + .send(); + const appAssignment = body.data as ApplicationAssignments; + evalTwofirstAssignment = appAssignment; + } + expect(evalOnefirstAssignment.applicant_id).toEqual(evalTwofirstAssignment.applicant_id); + + let evalThreefirstAssignment: ApplicationAssignments | null = null; + const evaluatorThree = 'evaluator_three'; + { + const { body, status } = await request + .get(`${assessmentRoute.fullBasePath}/${assessmentHurdleId}/next`) + .set('Authorization', `bearer ${evaluatorThree}`) + .send(); + const appAssignment = body.data as ApplicationAssignments; + evalThreefirstAssignment = appAssignment; + } + // but evaluator three shouldn't get this applicant + expect(evalThreefirstAssignment.applicant_id).not.toEqual(evalTwofirstAssignment.applicant_id); + done(); + }); + it('Flagging an applicant should remove this applicant from all queues', async done => { + const evalApplicant = new EvalSubmission(application); + + let evalOnefirstAssignment: ApplicationAssignments | null = null; + const evaluatorOne = 'evaluator_one'; + { + const { body, status } = await request + .get(`${assessmentRoute.fullBasePath}/${assessmentHurdleId}/next`) + .set('Authorization', `bearer ${evaluatorOne}`) + .send(); + const appAssignment = body.data as ApplicationAssignments; + evalOnefirstAssignment = appAssignment; + await evalApplicant.flagApplicant(assessmentHurdleId, evalOnefirstAssignment!.applicant_id, evaluatorOne); + } + + let evalTwofirstAssignment: ApplicationAssignments | null = null; + const evaluatorTwo = 'evaluator_two'; + { + const { body, status } = await request + .get(`${assessmentRoute.fullBasePath}/${assessmentHurdleId}/next`) + .set('Authorization', `bearer ${evaluatorTwo}`) + .send(); + const appAssignment = body.data as ApplicationAssignments; + evalTwofirstAssignment = appAssignment; + } + expect(evalOnefirstAssignment.applicant_id).not.toEqual(evalTwofirstAssignment.applicant_id); + + let evalThreefirstAssignment: ApplicationAssignments | null = null; + const evaluatorThree = 'evaluator_three'; + { + const { body, status } = await request + .get(`${assessmentRoute.fullBasePath}/${assessmentHurdleId}/next`) + .set('Authorization', `bearer ${evaluatorThree}`) + .send(); + const appAssignment = body.data as ApplicationAssignments; + evalThreefirstAssignment = appAssignment; + } + // but evaluator three shouldn't get this applicant + expect(evalOnefirstAssignment.applicant_id).not.toEqual(evalThreefirstAssignment.applicant_id); + + done(); + }); +}); diff --git a/api/src/tests/integration.tests/assignmentsQueue.int.spec.ts b/api/src/tests/integration.tests/assignmentsQueue.int.spec.ts new file mode 100644 index 0000000..2a6130b --- /dev/null +++ b/api/src/tests/integration.tests/assignmentsQueue.int.spec.ts @@ -0,0 +1,228 @@ +process.env.NODE_ENV = 'development'; +// process.env.APP_ENV = 'testing'; + +import path from 'path'; +import App from '../../app'; +import { AssessmentHurdle } from '../../models/assessment_hurdle'; +import AssessmentHurdleRoute from '../../routes/assessmenthurdle.routes'; +import EvaluationRoute from '../../routes/evaluation.routes'; +import IntegrationLoader from './util/integrationloader'; +import st, { SuperTest, Test } from 'supertest'; +import { ApplicationAssignments } from '../../models/application_assignments'; + +import EvalSubmission, { evaluator } from './util/evaluationSubmission'; + +const integrationLoader = new IntegrationLoader(path.join(__dirname, './data/demoResumeSingleGrade')); +const assessmentRoute = new AssessmentHurdleRoute(); +const evaluationRoute = new EvaluationRoute(); +const application: App = new App(9999, 'testing', [assessmentRoute, evaluationRoute]); + +describe('Queue exhaustion', () => { + let assessmentHurdleId: string; + let applicantCount: number; + let applicantIds: string[]; + + let request: SuperTest; + beforeAll(async done => { + try { + await application.serverReady; + request = st(application!.getServer()); + done(); + } catch (err) { + done(err); + } + }); + beforeEach(async done => { + try { + await application.serverReady; + const testingData = await integrationLoader.loadTestingData(true, true); + assessmentHurdleId = testingData.assessmentHurdleId; + applicantCount = testingData.applicantCount; + applicantIds = testingData.applicantIds; + done(); + } catch (err) { + done(err); + } + }); + it('A single SME should be able to exhaust their queue', async done => { + const evalApplicant = new EvalSubmission(application); + + const evaluator = 'evaluator_one'; + for (let evalsDone = 0; evalsDone < applicantCount; evalsDone++) { + const { body, status } = await request + .get(`${assessmentRoute.fullBasePath}/${assessmentHurdleId}/next`) + .set('Authorization', `bearer ${evaluator}`) + .send(); + const appAssignment = body.data as ApplicationAssignments; + expect(appAssignment).toBeTruthy(); + expect(appAssignment.applicant_id).toBeTruthy(); + await evalApplicant.evaluatePassing(assessmentHurdleId, appAssignment.applicant_id, evaluator); + } + const { body, status } = await request + .get(`${assessmentRoute.fullBasePath}/${assessmentHurdleId}/next`) + .set('Authorization', `bearer ${evaluator}`) + .send(); + const appAssignment = body.data; + expect(appAssignment).toBe(null); + + return done(); + }); + it('Two evaluators should exaust the queue for a third given a "2 evaluator minimum" requirement', async done => { + const evalApplicant = new EvalSubmission(application); + + { + const evaluatorOne = 'evaluator_one'; + for (let evalsDone = 0; evalsDone < applicantCount; evalsDone++) { + const { body, status } = await request + .get(`${assessmentRoute.fullBasePath}/${assessmentHurdleId}/next`) + .set('Authorization', `bearer ${evaluatorOne}`) + .send(); + const appAssignment = body.data as ApplicationAssignments; + expect(appAssignment).toBeTruthy(); + expect(appAssignment.applicant_id).toBeTruthy(); + await evalApplicant.evaluatePassing(assessmentHurdleId, appAssignment.applicant_id, evaluatorOne); + } + const { body, status } = await request + .get(`${assessmentRoute.fullBasePath}/${assessmentHurdleId}/next`) + .set('Authorization', `bearer ${evaluatorOne}`) + .send(); + const appAssignment = body.data; + expect(appAssignment).toBe(null); + } + { + const evaluatorTwo = 'evaluator_two'; + for (let evalsDone = 0; evalsDone < applicantCount; evalsDone++) { + const { body, status } = await request + .get(`${assessmentRoute.fullBasePath}/${assessmentHurdleId}/next`) + .set('Authorization', `bearer ${evaluatorTwo}`) + .send(); + const appAssignment = body.data as ApplicationAssignments; + expect(appAssignment).toBeTruthy(); + expect(appAssignment.applicant_id).toBeTruthy(); + await evalApplicant.evaluatePassing(assessmentHurdleId, appAssignment.applicant_id, evaluatorTwo); + } + const { body, status } = await request + .get(`${assessmentRoute.fullBasePath}/${assessmentHurdleId}/next`) + .set('Authorization', `bearer ${evaluatorTwo}`) + .send(); + const appAssignment = body.data; + expect(appAssignment).toBe(null); + } + { + const evaluatorThree = 'evaluator_three'; + + const { body, status } = await request + .get(`${assessmentRoute.fullBasePath}/${assessmentHurdleId}/next`) + .set('Authorization', `bearer ${evaluatorThree}`) + .send(); + const appAssignment = body.data; + expect(appAssignment).toBe(null); + } + return done(); + }); + it('Two evaluators should create tie breakers for a third evaluator when reviewing', async done => { + /** + * set up testing + */ + const evalApplicant = new EvalSubmission(application); + + const evalMap = applicantIds.reduce((localEvalMap, applicantId, idx) => { + let evalOrder: ('pass' | 'fail' | null | 'recuse' | 'flag')[]; + let evaluatorsDone: boolean[]; + switch (idx) { + case 0: + evalOrder = ['pass', 'fail', 'pass', null, null]; + evaluatorsDone = [false, false, false, true, true]; + break; + case 1: + evalOrder = ['fail', 'pass', 'fail', null, null]; + evaluatorsDone = [false, false, false, true, true]; + + break; + case 2: + evalOrder = ['pass', 'fail', 'recuse', 'pass', null]; + evaluatorsDone = [false, false, false, false, true]; + + break; + case 3: + evalOrder = ['pass', 'recuse', 'pass', null, null]; + evaluatorsDone = [false, false, false, true, true]; + + break; + case 4: + evalOrder = ['fail', 'recuse', 'pass', 'fail', null]; + evaluatorsDone = [false, false, false, false, true]; + + break; + case 5: + evalOrder = ['pass', 'recuse', 'flag', null, null]; + evaluatorsDone = [false, false, false, true, true]; + + break; + case 6: + evalOrder = ['fail', 'recuse', 'fail', null, null]; + evaluatorsDone = [false, false, false, true, true]; + + break; + case 6: + evalOrder = ['fail', 'fail', null, null, null]; + evaluatorsDone = [false, false, true, true, true]; + + break; + default: + evalOrder = ['pass', 'pass', null, null, null]; + evaluatorsDone = [false, false, true, true, true]; + + break; + } + localEvalMap[applicantId] = { evalOrder, evaluatorsDone }; + return localEvalMap; + }, {} as { [applicantId: string]: { evaluatorsDone: boolean[]; evalOrder: ('pass' | 'fail' | null | 'recuse' | 'flag')[] } }); + + async function evaluateApplicants(evaluator: evaluator, evaluatorIdx: number) { + const applicantCountForEvaluator = Object.values(evalMap).map(a => a.evaluatorsDone[evaluatorIdx]).length; + for (let evalsDone = 0; evalsDone < applicantCountForEvaluator; evalsDone++) { + const { body, status } = await request + .get(`${assessmentRoute.fullBasePath}/${assessmentHurdleId}/next`) + .set('Authorization', `bearer ${evaluator}`) + .send(); + const appAssignment = body.data as ApplicationAssignments; + if (appAssignment) { + const evalType = evalMap[appAssignment.applicant_id].evalOrder[evaluatorIdx]; + const evalStatus = evalMap[appAssignment.applicant_id].evaluatorsDone[evaluatorIdx]; + expect(evalType).not.toBe(null); + expect(evalStatus).toBe(false); + + switch (evalType) { + case 'pass': + await evalApplicant.evaluatePassing(assessmentHurdleId, appAssignment.applicant_id, evaluator); + break; + case 'fail': + await evalApplicant.evaluateFailing(assessmentHurdleId, appAssignment.applicant_id, evaluator); + break; + case 'recuse': + await evalApplicant.recuseApplicant(assessmentHurdleId, appAssignment.applicant_id, evaluator); + break; + case 'flag': + await evalApplicant.flagApplicant(assessmentHurdleId, appAssignment.applicant_id, evaluator); + break; + } + evalMap[appAssignment.applicant_id].evaluatorsDone[evaluatorIdx] = true; + } + } + const { body, status } = await request + .get(`${assessmentRoute.fullBasePath}/${assessmentHurdleId}/next`) + .set('Authorization', `bearer ${evaluator}`) + .send(); + const appAssignment = body.data; + expect(appAssignment).toBe(null); + return; + } + await evaluateApplicants('evaluator_one', 0); + await evaluateApplicants('evaluator_two', 1); + await evaluateApplicants('evaluator_three', 2); + await evaluateApplicants('evaluator_four', 3); + await evaluateApplicants('evaluator_five', 4); + return done(); + }); +}); diff --git a/api/src/tests/integration.tests/data/demoResumeSingleGrade/assessmentHurdle.json b/api/src/tests/integration.tests/data/demoResumeSingleGrade/assessmentHurdle.json new file mode 100644 index 0000000..4951cad --- /dev/null +++ b/api/src/tests/integration.tests/data/demoResumeSingleGrade/assessmentHurdle.json @@ -0,0 +1,18 @@ +{ + "departmentName": "Office of Weaving", + "componentName": "Moirai Component", + "positionName": "Ruler of the Gods", + "assessmentName": "Resume Review", + "positionDetails": "(G1)", + "locations": "Olympus, Greece", + "startDatetime": "2020-12-16T21:00:00.000Z", + "endDatetime": "2020-01-22T21:00:00.000Z", + "hurdleDisplayType": 1, + "evaluationsRequired": 2, + "hrEmail": "zeus@olympus.com", + "hrName": "Zeus", + "vacancyId": "9999999999999999", + "assessmentId": "1234", + "passNor": "PASS", + "failNor": "FAIL" +} diff --git a/api/src/tests/integration.tests/data/demoResumeSingleGrade/competencies.json b/api/src/tests/integration.tests/data/demoResumeSingleGrade/competencies.json new file mode 100644 index 0000000..3432900 --- /dev/null +++ b/api/src/tests/integration.tests/data/demoResumeSingleGrade/competencies.json @@ -0,0 +1,144 @@ +{ + "competencies": [ + { + "name": "Cleverness", + "localId": "core_clever", + "definition": "The ideal candidate has an abundance of cleverness... \n\n similar to that of Odysseus during his trials.", + "requiredProficiencyDefinition": "Candidate shows a history of clever ideas, such as: \n\n - Trojan Horses \n - Escaping Cyclops \n - Surving Sirens", + "displayType": 1, + "screenOut": true, + "selectors": [ + { + "displayName": "Yes", + "pointValue": 1 + }, + { + "displayName": "No evidence of this competency", + "pointValue": 0, + "sortOrder": 1, + "defaultText": "Candidate resume shows no evidence of improvisation" + }, + { + "displayName": "Doesn’t meet the required proficiency level", + "pointValue": 0, + "sortOrder": 2, + "defaultText": "" + } + ] + }, + { + "name": "Epic", + "localId": "core_epic", + "definition": "The ideal candidate should have experience inspiring stories and songs about them; ideally through an oral history.", + "requiredProficiencyDefinition": "Has had at least one great feat that people are aware of.", + "displayType": 1, + "screenOut": true, + "selectors": [ + { + "displayName": "Yes", + "pointValue": 1 + }, + { + "displayName": "No evidence of this competency", + "pointValue": 0, + "sortOrder": 1, + "defaultText": "Candidate resume has no known feats." + }, + { + "displayName": "Doesn’t meet the required proficiency level", + "pointValue": 0, + "sortOrder": 2, + "defaultText": "" + } + ] + }, + { + "name": "Managing Supplicant Expectations", + "localId": "core_expectations", + "definition": "The ideal candidate has experience ensuring that supplicants do not have their expectations failed when asking for help.", + "requiredProficiencyDefinition": "Does not have a reputation for ignoring those in need.", + "displayType": 1, + "screenOut": true, + "selectors": [ + { + "displayName": "Yes", + "pointValue": 1 + }, + { + "displayName": "No evidence of this competency", + "pointValue": 0, + "sortOrder": 1, + "defaultText": "Candidate resume shows no evidence of helping those in need." + }, + { + "displayName": "Doesn’t meet the required proficiency level", + "pointValue": 0, + "sortOrder": 2, + "defaultText": "" + } + ] + }, + { + "name": "Human Centered", + "localId": "core_human", + "definition": "The ideal candidate focuses their efforts on the mortal struggle. The ideal applicant frequently transforms into human form to experience life as one of the people.", + "requiredProficiencyDefinition": "Has in-depth knowledge of the hopes, dreams, struggles, and tribulations of the human plight.", + "displayType": 1, + "screenOut": true, + "selectors": [ + { + "displayName": "Yes", + "pointValue": 1 + }, + { + "displayName": "No evidence of this competency", + "pointValue": 0, + "sortOrder": 1, + "defaultText": "Candidate resume only shows concern for the supernatural." + }, + { + "displayName": "Doesn’t meet the required proficiency level", + "pointValue": 0, + "sortOrder": 2, + "defaultText": "" + } + ] + }, + { + "name": "52 weeks of being immortal", + "localId": "experience_immortal", + "definition": "", + "requiredProficiencyDefinition": "The applicant has at least 52 weeks of experience being immortal.", + "displayType": 2, + "screenOut": true, + "selectors": [ + { + "displayName": "Yes", + "pointValue": 1 + }, + { + "displayName": "No", + "pointValue": 0 + } + ] + }, + { + "name": "Festivals", + "localId": "experience_festivals", + "definition": "", + "requiredProficiencyDefinition": "The applicant has at least one fesitval in their name.", + "displayType": 2, + "screenOut": true, + "selectors": [ + { + "displayName": "Yes", + "pointValue": 1 + }, + { + "displayName": "No", + "pointValue": 0 + } + ] + } + ] +} diff --git a/api/src/tests/integration.tests/data/demoResumeSingleGrade/specialties.json b/api/src/tests/integration.tests/data/demoResumeSingleGrade/specialties.json new file mode 100644 index 0000000..bdfa87e --- /dev/null +++ b/api/src/tests/integration.tests/data/demoResumeSingleGrade/specialties.json @@ -0,0 +1,10 @@ +{ + "specialties": [ + { + "name": "Core specialty GS 13", + "pointsRequired": 6, + "localId": "0000-00(Ruler-of-the-gods)", + "competencyLocalIds": ["core_clever", "core_epic", "core_expectations", "core_human", "experience_immortal", "experience_festivals"] + } + ] +} diff --git a/api/src/tests/integration.tests/data/demoResumeSingleGrade/users.json b/api/src/tests/integration.tests/data/demoResumeSingleGrade/users.json new file mode 100644 index 0000000..05fc4bd --- /dev/null +++ b/api/src/tests/integration.tests/data/demoResumeSingleGrade/users.json @@ -0,0 +1,39 @@ +{ + "userSetup": [ + { + "role": 1, + "users": [ + { + "email": "smeqa_reviewer@usds.gov", + "name": "Hr 1 Tester" + } + ] + }, + + { + "role": 2, + "users": [ + { + "email": "smeqa_evaluator_one@usds.gov", + "name": "SME 1 Tester" + }, + { + "email": "smeqa_evaluator_two@usds.gov", + "name": "SME 2 Tester" + }, + { + "email": "smeqa_evaluator_three@usds.gov", + "name": "SME 3 Tester" + }, + { + "email": "smeqa_evaluator_four@usds.gov", + "name": "SME 4 Tester" + }, + { + "email": "smeqa_evaluator_five@usds.gov", + "name": "SME 5 Tester" + } + ] + } + ] +} diff --git a/api/src/tests/integration.tests/util/evaluationSubmission.ts b/api/src/tests/integration.tests/util/evaluationSubmission.ts new file mode 100644 index 0000000..1510f91 --- /dev/null +++ b/api/src/tests/integration.tests/util/evaluationSubmission.ts @@ -0,0 +1,116 @@ +import request, { SuperTest, Test } from 'supertest'; + +import App from '../../../app'; +import EvaluationRoute from '../../../routes/evaluation.routes'; +import { CompetencyWithSelectors } from '../../../dto/applicantdisplay.dto'; +export type evaluator = 'evaluator_one' | 'evaluator_two' | 'evaluator_three' | 'evaluator_four' | 'evaluator_five'; +export default class EvalSubmission { + evaluationRoute = new EvaluationRoute(); + application: App; + request: SuperTest; + + constructor(existingApp: App | null) { + this.application = existingApp ?? new App(9751, 'testing', [this.evaluationRoute]); + this.request = request(this.application.getServer()); + } + async flagApplicant(hurdleId: string, applicantId: string, evaluator: evaluator, flagNote = 'Flag message') { + const flagUrl = `/api/evaluation/${hurdleId}/flag/${applicantId}`; + return await this.request.post(flagUrl).set('Authorization', `bearer ${evaluator}`).send({ + flagMessage: flagNote, + }); + } + async recuseApplicant(hurdleId: string, applicantId: string, evaluator: evaluator, recuseNote = 'This is a recusal note') { + const recuseUrl = `/api/evaluation/${hurdleId}/recuse/${applicantId}`; + return await this.request.put(recuseUrl).set('Authorization', `bearer ${evaluator}`).send(); + } + async evaluateFailing(hurdleId: string, applicantId: string, evaluator: evaluator, failingNote = 'Failing competency note') { + const failingComps = []; + let i = 0; + // What are you doing reviewing more than 15 comps? + while (i < 15) { + failingComps.push(i++); + } + return await this.evaluateApplicant(hurdleId, applicantId, evaluator, failingComps, [], failingNote); + } + + async evaluatePassing(hurdleId: string, applicantId: string, evaluator: evaluator, note = 'Application Note') { + const passingComps = []; + let i = 0; + // What are you doing reviewing more than 15 comps? + while (i < 15) { + passingComps.push(i++); + } + return await this.evaluateApplicant(hurdleId, applicantId, evaluator, [], passingComps, '', note); + } + async evaluateApplicant( + hurdleId: string, + applicantId: string, + evaluator: evaluator, + failureCompIdxs: number[], + passCompIdxs: number[] = [], + failureNote = '', + applicantNote = '', + ): Promise<{ applicant: string; applicantPass: boolean }> { + const displayUrl = `/api/evaluation/${hurdleId}/display/${applicantId}`; + const evalUrl = `/api/evaluation/${hurdleId}/submit/${applicantId}`; + let applicantPass = true; + return new Promise((resolve, reject) => { + this.request + .get(displayUrl) + .set('Authorization', `bearer ${evaluator}`) + .end((err, resp) => { + if (err) { + reject(err); + return; + } + + const competenciesRaw = resp.body.data.competencies as CompetencyWithSelectors[]; + const competencies = competenciesRaw.sort((a, b) => a.name.localeCompare(b.name)); + + const evalMap = competencies + .map((comp: CompetencyWithSelectors, idx) => { + if (failureCompIdxs.includes(idx)) { + const noSelector = + comp.selectors.find(compSelector => compSelector.display_name === 'No evidence of this competency') || + comp.selectors.find(compSelector => compSelector.display_name === 'No'); + applicantPass = false; + return { + competencyId: comp.id, + selectorId: noSelector!.id, + note: failureNote, + }; + } + if (passCompIdxs.includes(idx)) { + const yesSelector = comp.selectors.find(compSelector => compSelector.display_name === 'Yes'); + return { + competencyId: comp.id, + selectorId: yesSelector!.id, + }; + } + return null; + }) + .filter(a => a); + + const sendingEval = { + note: '', + competencyEvals: evalMap, + }; + if (applicantNote) { + sendingEval.note = applicantNote; + } + + this.request + .put(evalUrl) + .send(sendingEval) + .set('Authorization', `bearer ${evaluator}`) + .end((err1, resp1) => { + if (err1) { + reject(err1); + return; + } + resolve({ applicant: applicantId, applicantPass }); + }); + }); + }); + } +} diff --git a/api/src/tests/integration.tests/util/integrationloader.ts b/api/src/tests/integration.tests/util/integrationloader.ts new file mode 100644 index 0000000..655d218 --- /dev/null +++ b/api/src/tests/integration.tests/util/integrationloader.ts @@ -0,0 +1,159 @@ +import fs from 'fs'; +import path from 'path'; + +import { validate, ValidationError } from 'class-validator'; +import { plainToClass } from 'class-transformer'; +import { v4 as uuidv4 } from 'uuid'; + +import DB from '../../../database'; +import { logger } from '../../../utils/logger'; +import USASCsvReader from '../../../handlers/util/usasCsvReader'; + +import { Applicant } from '../../../models/applicant'; +import { AppUser } from '../../../models/app_user'; +import { AssessmentHurdle } from '../../../models/assessment_hurdle'; +import CreateAssessmentHurdleDto from '../../../dto/createassessmenthurdle.dto'; +import CreateCompetenciesDto from '../../../dto/createcompetencies.dto'; +import CreateSpecialtiesDto from '../../../dto/createspecialties.dto'; +import CreateHurdleUserDto from '../../../dto/createhurdleuser.dto'; + +import AssessmentHurdleService from '../../../services/assessmenthurdle.service'; +import CompetencyService from '../../../services/competency.service'; +import SpecialtyService from '../../../services/specialty.service'; +import ApplicantService from '../../../services/applicant.service'; +import UserService from '../../../services/users.service'; + +export enum IntegrationFiles { + Applicants = 'applicants.csv', + AssessmentHurdle = 'assessmentHurdle.json', + Competencies = 'competencies.json', + Specialties = 'specialties.json', + Users = 'users.json', +} + +export default class IntegrationLoader { + dataFolderPath: string; + db: DB; + + constructor(dataFolderPath: string) { + this.dataFolderPath = dataFolderPath; + this.checkPath(); + this.db = new DB(); + } + + private checkPath() { + const check = path.join(this.dataFolderPath, IntegrationFiles.Users); + if (!fs.existsSync(check)) { + throw new Error(`Data files do not exist at ${this.dataFolderPath}`); + } + } + + async checkDatabaseSchema() { + await this.db.sequelize.sync(); + } + + async loadTestingData( + randomizeMeta = false, + purgeFirst = false, + ): Promise<{ + assessmentHurdleId: string; + applicantCount: number; + applicantIds: string[]; + }> { + if (purgeFirst) { + logger.warn('Purging data'!); + await this.purgeTestingData(); + } + logger.info(`Loading Test Data from ${this.dataFolderPath}`); + const rawAssessmentHurdle: CreateAssessmentHurdleDto = JSON.parse(this.readJsonFile(IntegrationFiles.AssessmentHurdle)); + const validationHurdle = await validate(plainToClass(CreateAssessmentHurdleDto, rawAssessmentHurdle), { skipMissingProperties: true }); + if (validationHurdle.length > 0) this.throwValidationErrors(validationHurdle); + + if (randomizeMeta) { + // This will prevent meta collisions when it's not necessary to use meta data. + rawAssessmentHurdle.vacancyId = uuidv4(); + rawAssessmentHurdle.assessmentId = uuidv4(); + } + + const svc = new AssessmentHurdleService(); + const rst = await svc.upsert(rawAssessmentHurdle); + const assessmentHurdleId = rst.id; + + logger.info(`Created AssessmentHurdle ${rst.id}`); + + const rawCompetencies: CreateCompetenciesDto = JSON.parse(this.readJsonFile(IntegrationFiles.Competencies)); + + rawCompetencies.assessmentHurdleId = rst.id; + rawCompetencies.competencies.forEach(rc => { + rc.assessmentHurdleId = rst.id; + }); + + const validationComps = await validate(plainToClass(CreateCompetenciesDto, rawCompetencies), { skipMissingProperties: true }); + if (validationComps.length > 0) this.throwValidationErrors(validationComps); + + const compService = new CompetencyService(); + + const competencies = await compService.upsertAll(rawCompetencies, assessmentHurdleId); + logger.info(`Created ${competencies.length} Competencies`); + + const rawSpecialties: CreateSpecialtiesDto = JSON.parse(this.readJsonFile(IntegrationFiles.Specialties)); + + const validationSpecialties = await validate(plainToClass(CreateSpecialtiesDto, rawSpecialties), { skipMissingProperties: true }); + if (validationSpecialties.length > 0) this.throwValidationErrors(validationComps); + + const specService = new SpecialtyService(); + const specialties = await specService.upsertAll(rawSpecialties, rst.id); + logger.info(`Created ${specialties.length} Specialties`); + + const applicantService = new ApplicantService(); + const csvPath = path.join(this.dataFolderPath, IntegrationFiles.Applicants); + const rawApplications = await USASCsvReader.parseFile(csvPath); + + const applicants = await applicantService.bulkUSASUpsert(rawApplications, rst.id); + logger.info(`Created ${applicants.length} applicants`); + + const rawUsers: CreateHurdleUserDto = JSON.parse(this.readJsonFile(IntegrationFiles.Users)); + const validationUsers = await validate(plainToClass(CreateHurdleUserDto, rawUsers), { skipMissingProperties: true }); + if (validationUsers.length > 0) this.throwValidationErrors(validationComps); + + const userService = new UserService(); + + const users = await userService.createUserAndAddToHurdle(rawUsers, assessmentHurdleId); + logger.info(`Created ${users.length} Users`); + + await rst.reload(); + return { + assessmentHurdleId: rst.id, + applicantCount: applicants.length, + applicantIds: applicants.map(a => a.id), + }; + } + + private async purgeTestingData() { + await Applicant.destroy({ + truncate: true, + cascade: true, + }); + await AssessmentHurdle.destroy({ + truncate: true, + cascade: true, + }); + await AppUser.destroy({ + truncate: true, + cascade: true, + }); + } + + private readJsonFile(file: IntegrationFiles): string { + return fs.readFileSync(path.join(this.dataFolderPath, file), { + encoding: 'utf-8', + flag: 'r', + }); + } + + private throwValidationErrors(validationHurdle: ValidationError[]) { + // @ts-ignore + const message = validationHurdle.map((error: ValidationError) => Object.values(error.constraints)).join(', '); + throw new Error(message); + } +} diff --git a/api/src/tests/mockDB.ts b/api/src/tests/mockDB.ts new file mode 100644 index 0000000..20ab706 --- /dev/null +++ b/api/src/tests/mockDB.ts @@ -0,0 +1,30 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { QueryOptionsWithType, QueryTypes, Transaction } from 'sequelize/types'; +import { DBInterface } from '../database'; +import { mocked } from 'ts-jest/utils'; + +export default class MockDB implements DBInterface { + public sequelize = { + transaction: mocked(({} as unknown) as Transaction), + query: jest.fn(), + }; + + // eslint-disable-next-line @typescript-eslint/no-empty-function + initConnection(): void {} + async transaction(autoCallback: (t: Transaction) => PromiseLike): Promise { + return autoCallback(this.sequelize.transaction); + } + + // eslint-disable-next-line @typescript-eslint/ban-types + async query( + sql: string | { query: string; values: unknown[] }, + options: QueryOptionsWithType & { plain: true }, + ): Promise { + return new Promise((reject, resolve) => { + resolve(this.sequelize.query()); + }); + } + + // eslint-disable-next-line @typescript-eslint/no-empty-function + close(): void {} +} diff --git a/api/src/tests/mockEvaluationSubmitter.ts b/api/src/tests/mockEvaluationSubmitter.ts new file mode 100644 index 0000000..7672c46 --- /dev/null +++ b/api/src/tests/mockEvaluationSubmitter.ts @@ -0,0 +1,63 @@ +import App from '../app'; +import AssessmentHurdleRoute from '../routes/assessmenthurdle.routes'; +import EvaluationRoute from '../routes/evaluation.routes'; +import request from 'supertest'; +import { CompetencyWithSelectors } from '../dto/applicantdisplay.dto'; +import { ApplicationEvaluation } from '../models/application_evaluation'; +import { CompetencyEvaluation } from '../models/competency_evaluation'; + +export default class MockEvaluationSubmitter { + assessmentRoute = new AssessmentHurdleRoute(); + evaluationRoute = new EvaluationRoute(); + application: App; + + constructor(existingApp: App | null) { + this.application = existingApp ?? new App(9999, 'testing', [this.assessmentRoute, this.evaluationRoute]); + } + + async submit(hurdleId: string, applicantId: string, evaluatorId: string): Promise<[ApplicationEvaluation[], CompetencyEvaluation[]]> { + const displayUrl = `evaluation/${hurdleId}/display/${applicantId}`; + const evalUrl = `evaluation/${hurdleId}/submit/${applicantId}`; + + return new Promise((reject, resolve) => { + request(this.application.getServer()) + .get(displayUrl) + .set('Authorization', 'bearer evaluator_two') + .end((err, resp) => { + if (err) { + reject(err); + return; + } + + const data = resp.body.data.data; + const competencies = data.competencies; + const evalMap = competencies.map((comp: CompetencyWithSelectors) => { + const yesSelector = comp.selectors.filter(compSelector => compSelector.display_name === 'Yes')[0]; + return { + competencyId: comp.id, + selectorId: yesSelector.id, + note: 'Some Fancy Note', + }; + }); + + const sendingEval = { + note: 'Application Note', + evaluatorId: evaluatorId, + competencyEvals: evalMap, + }; + + request(this.application.getServer()) + .put(evalUrl) + .send(sendingEval) + .set('Authorization', 'bearer evaluator_two') + .end((err1, resp1) => { + if (err1) { + reject(err1); + return; + } + resolve(resp1.body.data); + }); + }); + }); + } +} diff --git a/api/src/tests/services.tests/applicant.service.spec.ts b/api/src/tests/services.tests/applicant.service.spec.ts new file mode 100644 index 0000000..5019b14 --- /dev/null +++ b/api/src/tests/services.tests/applicant.service.spec.ts @@ -0,0 +1,102 @@ +import HttpException from '../../exceptions/HttpException'; +import { Applicant } from '../../models/applicant'; +import { ApplicantMeta } from '../../models/applicant_meta'; +import { Application } from '../../models/application'; +import { ApplicationMeta } from '../../models/application_meta'; +import ApplicantService from '../../services/applicant.service'; +import { Specialty } from '../../models/specialty'; +import BulkUSASApplicationsDto, { USASApplicationDto } from '../../dto/BulkApplicantApplications.dto'; + +jest.mock('../../models/applicant'); +jest.mock('../../models/specialty'); +beforeEach(() => { + jest.resetAllMocks(); +}); + +const ASSESSMENT_HURDLE_ID = '542e7535-3ab5-494d-ad08-b6b0bbc9abb9'; +const INVALID_HURDLE_ID = '92aebbb5-94a1-4c7d-9b3c-c2fae5691122'; +const STEVE_TESTER = { + id: '0f0fb145-5e26-4732-b72d-73560645a544', + name: 'Steve Tester', + assessment_hurdle_id: ASSESSMENT_HURDLE_ID, + created_at: new Date(), + updated_at: new Date(), +}; +const STEVE_TESTER_META = { + id: '3482edff-5f9e-4839-b3a8-7c5f9de4ae18', + staffing_first_name: 'Steve', + staffing_last_name: 'Tester', + staffing_application_number: '098765432', + staffing_application_id: '34567890', + applicant_id: '0f0fb145-5e26-4732-b72d-73560645a544', +}; + +const nullApplicant = { + id: 'f4486698-7209-46e7-9069-73a3cf3debca', +}; +describe('Applicant service getBy Tests', () => { + it('getById returns value', async () => { + const mockFindByPk = jest.fn(); + mockFindByPk.mockReturnValue(STEVE_TESTER); + Applicant.findByPk = mockFindByPk; + const svc = new ApplicantService(); + const rst = await svc.getById(STEVE_TESTER.id); + expect(mockFindByPk).toBeCalledTimes(1); + expect(rst.assessment_hurdle_id).toEqual(STEVE_TESTER.assessment_hurdle_id); + }); + + it('getById returns 400 for empty input', async () => { + const svc = new ApplicantService(); + await expect(svc.getById('')).rejects.toThrowError(HttpException); + }); + it('getById returns 404 for not found value', async () => { + const mockFindByPk = jest.fn(); + mockFindByPk.mockReturnValue(undefined); + Applicant.findByPk = mockFindByPk; + const svc = new ApplicantService(); + + await expect(svc.getById(nullApplicant.id)).rejects.toThrowError(HttpException); + expect(mockFindByPk).toBeCalledTimes(1); + }); + + it('getByIdWithMeta returns values', async () => { + const mockMetaResolution = jest.fn(); + mockMetaResolution.mockReturnValue(STEVE_TESTER_META); + + const mockFindByPk = jest.fn(); + mockFindByPk.mockReturnValue({ ...STEVE_TESTER, getApplicantMetum: mockMetaResolution }); + + Applicant.findByPk = mockFindByPk; + const svc = new ApplicantService(); + + const [rst, rstMeta] = await svc.getByIdWithMeta(STEVE_TESTER.id, STEVE_TESTER.assessment_hurdle_id); + expect(mockFindByPk).toBeCalledTimes(1); + expect(mockMetaResolution).toBeCalledTimes(1); + expect(rst.id).toEqual(STEVE_TESTER.id); + expect(rstMeta.id).toEqual(STEVE_TESTER_META.id); + }); + + it('getByIdWithMeta returns 400 for empty input', async () => { + const svc = new ApplicantService(); + await expect(svc.getByIdWithMeta('', ASSESSMENT_HURDLE_ID)).rejects.toThrowError(HttpException); + }); + + it('getByIdWithMeta returns 404 for not found', async () => { + const mockFindByPk = jest.fn(); + mockFindByPk.mockReturnValue(undefined); + Applicant.findByPk = mockFindByPk; + const svc = new ApplicantService(); + await expect(svc.getByIdWithMeta(nullApplicant.id, ASSESSMENT_HURDLE_ID)).rejects.toThrowError(HttpException); + }); + it('getByIdWithMeta returns 403 mismatch on hurdleId', async () => { + const mockMetaResolution = jest.fn(); + mockMetaResolution.mockReturnValue(STEVE_TESTER_META); + + const mockFindByPk = jest.fn(); + mockFindByPk.mockReturnValue({ ...STEVE_TESTER, getApplicantMetum: mockMetaResolution }); + + Applicant.findByPk = mockFindByPk; + const svc = new ApplicantService(); + await expect(svc.getByIdWithMeta(STEVE_TESTER.id, INVALID_HURDLE_ID)).rejects.toThrowError(HttpException); + }); +}); diff --git a/api/src/tests/services.tests/applicant_bulk_upload.service.spec.ts b/api/src/tests/services.tests/applicant_bulk_upload.service.spec.ts new file mode 100644 index 0000000..89510b4 --- /dev/null +++ b/api/src/tests/services.tests/applicant_bulk_upload.service.spec.ts @@ -0,0 +1,379 @@ +import HttpException from '../../exceptions/HttpException'; +import { Applicant } from '../../models/applicant'; +import { ApplicantMeta } from '../../models/applicant_meta'; +import { Application } from '../../models/application'; +import { ApplicationMeta } from '../../models/application_meta'; +import ApplicantService from '../../services/applicant.service'; +import { Specialty } from '../../models/specialty'; +import BulkUSASApplicationsDto, { USASApplicationDto } from '../../dto/BulkApplicantApplications.dto'; + +jest.mock('../../models/applicant'); +jest.mock('../../models/specialty'); +beforeEach(() => { + jest.resetAllMocks(); +}); + +const ASSESSMENT_HURDLE_ID = '542e7535-3ab5-494d-ad08-b6b0bbc9abb9'; +const INVALID_HURDLE_ID = '92aebbb5-94a1-4c7d-9b3c-c2fae5691122'; +const STEVE_TESTER = { + id: '0f0fb145-5e26-4732-b72d-73560645a544', + name: 'Steve Tester', + assessment_hurdle_id: ASSESSMENT_HURDLE_ID, + created_at: new Date(), + updated_at: new Date(), +}; +const STEVE_TESTER_META = { + id: '3482edff-5f9e-4839-b3a8-7c5f9de4ae18', + staffing_first_name: 'Steve', + staffing_last_name: 'Tester', + staffing_application_number: '098765432', + staffing_application_id: '34567890', + applicant_id: '0f0fb145-5e26-4732-b72d-73560645a544', +}; + +const nullApplicant = { + id: 'f4486698-7209-46e7-9069-73a3cf3debca', +}; +describe('Applicant service bulk tests', () => { + it('bulkUSASUpsert maps specialties by localID and upserts multiple applications', async () => { + const svc = new ApplicantService(); + const mockGetSpecialtyByHurdleId = jest.fn(); + const hurdleId = '542e7535-3ab5-494d-ad08-b6b0bbc9abb9'; + const core_13Id = '000e7535-3ab5-494d-ad08-b6b0bbc9abb9'; + const core_14Id = '001e7535-3ab5-494d-ad08-b6b0bbc9abb9'; + const spec1_13Id = '100e7535-3ab5-494d-ad08-b6b0bbc9abb9'; + const spec1_14Id = '101e7535-3ab5-494d-ad08-b6b0bbc9abb9'; + const spec2_13Id = '200e7535-3ab5-494d-ad08-b6b0bbc9abb9'; + const spec2_14Id = '201e7535-3ab5-494d-ad08-b6b0bbc9abb9'; + + mockGetSpecialtyByHurdleId.mockReturnValue([ + { + id: core_13Id, + name: 'core', + local_id: '0000-13(Core)', + assessment_hurdle_id: hurdleId, + points_required: 4, + created_at: new Date(), + updated_at: new Date(), + }, + { + id: core_14Id, + name: 'core', + local_id: '0000-14(Core)', + assessment_hurdle_id: hurdleId, + points_required: 40, + created_at: new Date(), + updated_at: new Date(), + }, + { + id: spec1_13Id, + name: 'Specialty one', + local_id: '0000-13(Specialty 1)', + assessment_hurdle_id: hurdleId, + points_required: 5, + created_at: new Date(), + updated_at: new Date(), + }, + { + id: spec1_14Id, + name: 'Specialty one', + local_id: '0000-14(Specialty 1)', + assessment_hurdle_id: hurdleId, + points_required: 50, + created_at: new Date(), + updated_at: new Date(), + }, + { + id: spec2_13Id, + name: 'Specialty two', + local_id: '0000-13(Specialty 2)', + assessment_hurdle_id: hurdleId, + points_required: 5, + created_at: new Date(), + updated_at: new Date(), + }, + , + { + id: spec2_14Id, + name: 'Specialty two', + local_id: '0000-14(Specialty 2)', + assessment_hurdle_id: hurdleId, + points_required: 50, + created_at: new Date(), + updated_at: new Date(), + }, + ]); + const mockUpsertApplicant = jest.fn(); + mockUpsertApplicant.mockImplementation(e => [{ ...e, id: 'app_id' }]); + const mockFindOrCreateApplicantMeta = jest.fn(); + mockFindOrCreateApplicantMeta.mockImplementation(e => [e, e]); + const mockUpsertApplication = jest.fn(); + let core13Count = 0; + let core14Count = 0; + let spec1_13Count = 0; + let spec1_14Count = 0; + let spec2_13Count = 0; + let spec2_14Count = 0; + mockUpsertApplication.mockImplementation(e => { + expect(e.applicant_id).toBeTruthy(); + switch (e.specialty_id) { + case core_13Id: + core13Count++; + break; + case core_14Id: + core14Count++; + break; + case spec1_13Id: + spec1_13Count++; + break; + case spec1_14Id: + spec1_14Count++; + break; + case spec2_13Id: + spec2_13Count++; + break; + case spec2_14Id: + spec2_14Count++; + break; + default: + throw new Error('No specialty Id'); + } + return [e]; + }); + const mockFindOrCreateApplicationMeta = jest.fn(); + mockFindOrCreateApplicationMeta.mockImplementation(e => [e, e]); + Specialty.findAll = mockGetSpecialtyByHurdleId; + + Applicant.upsert = mockUpsertApplicant; + ApplicantMeta.findOrCreate = mockFindOrCreateApplicantMeta; + Application.upsert = mockUpsertApplication; + ApplicationMeta.findOrCreate = mockFindOrCreateApplicationMeta; + + const body = new BulkUSASApplicationsDto(); + const applicantOne = { + firstName: 'Applicant 1 First Name', + lastName: 'Applicant 1 Last Name', + middleName: 'Applicant 1 Middle Name', + applicationId: '1111', + applicationNumber: 'AAAA', + }; + const applicantTwo = { + firstName: 'Applicant 2 First Name', + lastName: 'Applicant 2 Last Name', + middleName: 'Applicant 2 Middle Name', + applicationId: '2222', + applicationNumber: 'BBBB', + }; + const applicantThree = { + firstName: 'Applicant 3 First Name', + lastName: 'Applicant 3 Last Name', + middleName: 'Applicant 3 Middle Name', + applicationId: '3333', + applicationNumber: 'CCCC', + }; + const assessmentId = '0000'; + + // application.staffingRatingCombination = row['Rating Combination']; + // application.staffingRatingId = row['Application Rating ID']; + body.applications = [ + { ...applicantOne, staffingRatingCombination: '0000-13(Core)', staffingRatingId: '1-1' }, + { ...applicantOne, staffingRatingCombination: '0000-14(Core)', staffingRatingId: '1-2' }, + { ...applicantOne, staffingRatingCombination: '0000-13(Specialty 1)', staffingRatingId: '1-3' }, + { ...applicantOne, staffingRatingCombination: '0000-14(Specialty 1)', staffingRatingId: '1-4' }, + { ...applicantOne, staffingRatingCombination: '0000-13(Specialty 2)', staffingRatingId: '1-5' }, + { ...applicantOne, staffingRatingCombination: '0000-14(Specialty 2)', staffingRatingId: '1-6' }, + { ...applicantTwo, staffingRatingCombination: '0000-13(Core)', staffingRatingId: '1-7' }, + { ...applicantTwo, staffingRatingCombination: '0000-14(Core)', staffingRatingId: '1-8' }, + { ...applicantTwo, staffingRatingCombination: '0000-13(Specialty 1)', staffingRatingId: '1-9' }, + { ...applicantTwo, staffingRatingCombination: '0000-14(Specialty 1)', staffingRatingId: '1-10' }, + { ...applicantTwo, staffingRatingCombination: '0000-13(Specialty 2)', staffingRatingId: '1-11' }, + { ...applicantTwo, staffingRatingCombination: '0000-14(Specialty 2)', staffingRatingId: '1-12' }, + { ...applicantThree, staffingRatingCombination: '0000-13(Core)', staffingRatingId: '1-13' }, + + { ...applicantThree, staffingRatingCombination: '0000-13(Specialty 1)', staffingRatingId: '1-14' }, + + { ...applicantThree, staffingRatingCombination: '0000-13(Specialty 2)', staffingRatingId: '1-15' }, + ].map(a => { + const application = new USASApplicationDto(); + application.firstName = a.firstName; + application.lastName = a.lastName; + application.middleName = a.middleName; + application.staffingApplicationId = a.applicationId; + application.staffingApplicationNumber = a.applicationNumber; + application.staffingRatingId = a.staffingRatingId; + application.staffingAssessmentId = assessmentId; + application.staffingRatingCombination = a.staffingRatingCombination; + return application; + }); + + const applicants = await svc.bulkUSASUpsert(body, hurdleId); + const totalApplicantCount = 3; + const totalApplicationsCount = 15; + + expect(applicants.length).toBe(totalApplicantCount); + const applicantNames = applicants.map(a => a.name); + expect(applicantNames).toContain(`${applicantOne.firstName} ${applicantOne.middleName} ${applicantOne.lastName}`); + expect(applicantNames).toContain(`${applicantTwo.firstName} ${applicantTwo.middleName} ${applicantTwo.lastName}`); + expect(applicantNames).toContain(`${applicantThree.firstName} ${applicantThree.middleName} ${applicantThree.lastName}`); + // Validate that specialty matching works as expected + expect(core13Count).toBe(body.applications.filter(e => e.staffingRatingCombination === '0000-13(Core)').length); + expect(core14Count).toBe(body.applications.filter(e => e.staffingRatingCombination === '0000-14(Core)').length); + expect(spec1_13Count).toBe(body.applications.filter(e => e.staffingRatingCombination === '0000-13(Specialty 1)').length); + expect(spec1_14Count).toBe(body.applications.filter(e => e.staffingRatingCombination === '0000-14(Specialty 1)').length); + expect(spec2_13Count).toBe(body.applications.filter(e => e.staffingRatingCombination === '0000-13(Specialty 2)').length); + expect(spec2_14Count).toBe(body.applications.filter(e => e.staffingRatingCombination === '0000-14(Specialty 2)').length); + // Valdidate correct calls for upserts are made + expect(mockUpsertApplicant).toBeCalledTimes(totalApplicantCount); + expect(mockFindOrCreateApplicantMeta).toBeCalledTimes(totalApplicantCount); + expect(mockUpsertApplication).toBeCalledTimes(totalApplicationsCount); + expect(mockFindOrCreateApplicationMeta).toBeCalledTimes(totalApplicationsCount); + }); + + it('bulkUSASUpsert fails if the applications cannot match specialties', async () => { + const svc = new ApplicantService(); + const mockGetSpecialtyByHurdleId = jest.fn(); + const hurdleId = '542e7535-3ab5-494d-ad08-b6b0bbc9abb9'; + const core_13Id = '000e7535-3ab5-494d-ad08-b6b0bbc9abb9'; + const core_14Id = '001e7535-3ab5-494d-ad08-b6b0bbc9abb9'; + const spec1_13Id = '100e7535-3ab5-494d-ad08-b6b0bbc9abb9'; + const spec1_14Id = '101e7535-3ab5-494d-ad08-b6b0bbc9abb9'; + const spec2_13Id = '200e7535-3ab5-494d-ad08-b6b0bbc9abb9'; + const spec2_14Id = '201e7535-3ab5-494d-ad08-b6b0bbc9abb9'; + + mockGetSpecialtyByHurdleId.mockReturnValue([ + { + id: core_13Id, + name: 'core', + local_id: '0000-13(Core)', + assessment_hurdle_id: hurdleId, + points_required: 4, + created_at: new Date(), + updated_at: new Date(), + }, + { + id: core_14Id, + name: 'core', + local_id: '0000-14(Core)', + assessment_hurdle_id: hurdleId, + points_required: 40, + created_at: new Date(), + updated_at: new Date(), + }, + { + id: spec1_13Id, + name: 'Specialty one', + local_id: '0000-13(Specialty 1)', + assessment_hurdle_id: hurdleId, + points_required: 5, + created_at: new Date(), + updated_at: new Date(), + }, + { + id: spec1_14Id, + name: 'Specialty one', + local_id: '0000-14(Specialty 1)', + assessment_hurdle_id: hurdleId, + points_required: 50, + created_at: new Date(), + updated_at: new Date(), + }, + { + id: spec2_13Id, + name: 'Specialty two', + local_id: '0000-13(Specialty 2)', + assessment_hurdle_id: hurdleId, + points_required: 5, + created_at: new Date(), + updated_at: new Date(), + }, + , + { + id: spec2_14Id, + name: 'Specialty two', + local_id: '0000-14(Specialty 2)', + assessment_hurdle_id: hurdleId, + points_required: 50, + created_at: new Date(), + updated_at: new Date(), + }, + ]); + const mockUpsertApplicant = jest.fn(); + mockUpsertApplicant.mockImplementation(e => [{ ...e, id: 'app_id' }]); + const mockFindOrCreateApplicantMeta = jest.fn(); + mockFindOrCreateApplicantMeta.mockImplementation(e => [e, e]); + const mockUpsertApplication = jest.fn(); + mockUpsertApplication.mockImplementation(e => { + return [e]; + }); + const mockFindOrCreateApplicationMeta = jest.fn(); + mockFindOrCreateApplicationMeta.mockImplementation(e => [e, e]); + Specialty.findAll = mockGetSpecialtyByHurdleId; + + Applicant.upsert = mockUpsertApplicant; + ApplicantMeta.findOrCreate = mockFindOrCreateApplicantMeta; + Application.upsert = mockUpsertApplication; + ApplicationMeta.findOrCreate = mockFindOrCreateApplicationMeta; + + const body = new BulkUSASApplicationsDto(); + const applicantOne = { + firstName: 'Applicant 1 First Name', + lastName: 'Applicant 1 Last Name', + middleName: 'Applicant 1 Middle Name', + applicationId: '1111', + applicationNumber: 'AAAA', + }; + const applicantTwo = { + firstName: 'Applicant 2 First Name', + lastName: 'Applicant 2 Last Name', + middleName: 'Applicant 2 Middle Name', + applicationId: '2222', + applicationNumber: 'BBBB', + }; + const applicantThree = { + firstName: 'Applicant 3 First Name', + lastName: 'Applicant 3 Last Name', + middleName: 'Applicant 3 Middle Name', + applicationId: '3333', + applicationNumber: 'CCCC', + }; + const assessmentId = '0000'; + + // application.staffingRatingCombination = row['Rating Combination']; + // application.staffingRatingId = row['Application Rating ID']; + body.applications = [ + { ...applicantOne, staffingRatingCombination: '0000-13(Core)', staffingRatingId: '1-1' }, + { ...applicantOne, staffingRatingCombination: '0000-14(Core)', staffingRatingId: '1-2' }, + { ...applicantOne, staffingRatingCombination: '0000-13(Specialty 1)', staffingRatingId: '1-3' }, + { ...applicantOne, staffingRatingCombination: '0000-14(Specialty 1)', staffingRatingId: '1-4' }, + { ...applicantOne, staffingRatingCombination: '0000-13(Specialty 2)', staffingRatingId: '1-5' }, + { ...applicantOne, staffingRatingCombination: '0000-14(Specialty 2)', staffingRatingId: '1-6' }, + { ...applicantTwo, staffingRatingCombination: 'mismatched ratingCombination', staffingRatingId: '1-7' }, + { ...applicantTwo, staffingRatingCombination: '0000-14(Core)', staffingRatingId: '1-8' }, + { ...applicantTwo, staffingRatingCombination: '0000-13(Specialty 1)', staffingRatingId: '1-9' }, + { ...applicantTwo, staffingRatingCombination: '0000-14(Specialty 1)', staffingRatingId: '1-10' }, + { ...applicantTwo, staffingRatingCombination: '0000-13(Specialty 2)', staffingRatingId: '1-11' }, + { ...applicantTwo, staffingRatingCombination: '0000-14(Specialty 2)', staffingRatingId: '1-12' }, + { ...applicantThree, staffingRatingCombination: '0000-13(Core)', staffingRatingId: '1-13' }, + + { ...applicantThree, staffingRatingCombination: '0000-13(Specialty 1)', staffingRatingId: '1-14' }, + + { ...applicantThree, staffingRatingCombination: '0000-13(Specialty 2)', staffingRatingId: '1-15' }, + ].map(a => { + const application = new USASApplicationDto(); + application.firstName = a.firstName; + application.lastName = a.lastName; + application.middleName = a.middleName; + application.staffingApplicationId = a.applicationId; + application.staffingApplicationNumber = a.applicationNumber; + application.staffingRatingId = a.staffingRatingId; + application.staffingAssessmentId = assessmentId; + application.staffingRatingCombination = a.staffingRatingCombination; + return application; + }); + await expect(svc.bulkUSASUpsert(body, hurdleId)).rejects.toThrowError(HttpException); + expect(mockUpsertApplicant).toBeCalledTimes(0); + expect(mockFindOrCreateApplicantMeta).toBeCalledTimes(0); + expect(mockUpsertApplication).toBeCalledTimes(0); + expect(mockFindOrCreateApplicationMeta).toBeCalledTimes(0); + }); +}); diff --git a/api/src/tests/services.tests/application.service.spec.ts b/api/src/tests/services.tests/application.service.spec.ts new file mode 100644 index 0000000..4752c37 --- /dev/null +++ b/api/src/tests/services.tests/application.service.spec.ts @@ -0,0 +1,78 @@ +import HttpException from '../../exceptions/HttpException'; +import { Application } from '../../models/application'; +import ApplicationService from '../../services/application.service'; + +jest.mock('../../models/application'); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('getBy Tests', () => { + it('getById returns value', async () => { + const mockFindByPk = jest.fn(); + mockFindByPk.mockReturnValue({ + id: 'f3a04475-bcb2-4c8b-96cd-d7fddbfe1adb', + applicant_id: '6afa6a30-2a34-404a-b2ec-a42a0b3e3110', + specialty_id: '4f2154f8-1221-484d-bd3c-8edddf999e33', + updated_at: new Date(), + created_at: new Date(), + }); + Application.findByPk = mockFindByPk; + const svc = new ApplicationService(); + const rst = await svc.getById('f3a04475-bcb2-4c8b-96cd-d7fddbfe1adb'); + expect(mockFindByPk).toBeCalledTimes(1); + expect(rst.id).toEqual('f3a04475-bcb2-4c8b-96cd-d7fddbfe1adb'); + }); + it('getById returns 400 for empty input', async () => { + const svc = new ApplicationService(); + await expect(svc.getById('')).rejects.toThrowError(HttpException); + }); + it('getById returns 404 for not found value', async () => { + const mockFindByPk = jest.fn(); + mockFindByPk.mockReturnValue(undefined); + Application.findByPk = mockFindByPk; + const svc = new ApplicationService(); + await expect(svc.getById('f3a04475-bcb2-4c8b-96cd-d7fddbfe1adb')).rejects.toThrowError(HttpException); + expect(mockFindByPk).toBeCalledTimes(1); + }); + it('getByIdWithMeta returns value', async () => { + const mockMetaResolution = jest.fn(); + mockMetaResolution.mockReturnValue({ + id: '0d3098b8-9d39-491c-93c2-389fc0de39cb', + application_id: '2c1332c1-8b8e-40b4-b135-3583a37f9e69', + staffing_application_rating_id: 'string', + staffing_assessment_id: 'string', + staffing_rating_combination: 'string', + created_at: new Date(), + updated_at: new Date(), + }); + + const mockFindByPk = jest.fn(); + mockFindByPk.mockReturnValue({ + id: '2c1332c1-8b8e-40b4-b135-3583a37f9e69', + applicant_id: '6afa6a30-2a34-404a-b2ec-a42a0b3e3110', + specialty_id: '4f2154f8-1221-484d-bd3c-8edddf999e33', + updated_at: new Date(), + created_at: new Date(), + getApplicationMetum: mockMetaResolution, + }); + + Application.findByPk = mockFindByPk; + const svc = new ApplicationService(); + const [rst, rstMeta] = await svc.getByIdWithMeta('6afa6a30-2a34-404a-b2ec-a42a0b3e3110'); + expect(mockFindByPk).toBeCalledTimes(1); + expect(mockMetaResolution).toBeCalledTimes(1); + expect(rst.id).toEqual('2c1332c1-8b8e-40b4-b135-3583a37f9e69'); + expect(rstMeta.id).toEqual('0d3098b8-9d39-491c-93c2-389fc0de39cb'); + }); + it('getByIdWithMeta returns 400 for empty input', async () => { + const svc = new ApplicationService(); + await expect(svc.getByIdWithMeta('')).rejects.toThrowError(HttpException); + }); + it('getByIdWithMeta returns 404 for not found', async () => { + const mockFindByPk = jest.fn(); + mockFindByPk.mockReturnValue(undefined); + Application.findByPk = mockFindByPk; + }); +}); diff --git a/api/src/tests/services.tests/applicationassignment.service.spec.ts b/api/src/tests/services.tests/applicationassignment.service.spec.ts new file mode 100644 index 0000000..6295a4c --- /dev/null +++ b/api/src/tests/services.tests/applicationassignment.service.spec.ts @@ -0,0 +1,269 @@ +import HttpException from '../../exceptions/HttpException'; +import { ApplicationAssignments, ApplicationAssignmentsAttributes } from '../../models/application_assignments'; +import { AssessmentHurdleUser } from '../../models/assessment_hurdle_user'; +import ApplicationAssignmentService from '../../services/application_assignment.service'; +import { ApplicantRecusals } from '../../models/applicant_recusals'; + +jest.mock('../../models/application_assignments'); +jest.mock('../../models/applicant'); +jest.mock('../../models/applicant_recusals'); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('nextQueue tests', () => { + it('empty parameters throw 403', async () => { + const svc = new ApplicationAssignmentService(); + + await expect(svc.nextQueue('', '')).rejects.toThrowError(HttpException); + }); + it('currentUser is not part of hurdle throws 403', async () => { + const mockCount = jest.fn(); + mockCount.mockReturnValue(0); + + AssessmentHurdleUser.count = mockCount; + + const svc = new ApplicationAssignmentService(); + await expect(svc.nextQueue('02f84756-5a81-4512-a618-7d318fe12bd4', '4c0b8a4a-1810-4a62-9323-e95d52fe9c28')).rejects.toThrowError(HttpException); + }); + it('existing active queue record returns existing', async () => { + const mockCount = jest.fn(); + mockCount.mockReturnValue(1); + + AssessmentHurdleUser.count = mockCount; + const mockRescusalsCount = jest.fn(); + mockRescusalsCount.mockReturnValue(0); + ApplicantRecusals.count = mockRescusalsCount; + + const mockExisting = jest.fn(); + const existing: ApplicationAssignmentsAttributes = { + id: '0e887e46-7202-40b9-8283-602bb7472d77', + applicant_id: '126a71c9-3ea9-4682-b42c-7541df0955d8', + assessment_hurdle_id: 'c8fadb4f-9f09-4d34-a8d2-ad2ed04f98a1', + evaluator_id: 'aab062d1-1649-4fac-b5d7-1e33ca0d0605', + active: true, + updated_at: new Date(), + created_at: new Date(), + }; + mockExisting.mockReturnValue(existing); + ApplicationAssignments.findOne = mockExisting; + const svc = new ApplicationAssignmentService(); + const rst = await svc.nextQueue('c8fadb4f-9f09-4d34-a8d2-ad2ed04f98a1', 'aab062d1-1649-4fac-b5d7-1e33ca0d0605'); + expect(rst).not.toBeNull(); + expect(rst!.applicant_id).toEqual('0e887e46-7202-40b9-8283-602bb7472d77'); + }); + // it('empty queue returns null', async () => { + // const mockCount = jest.fn(); + // mockCount.mockReturnValue(1); + + // AssessmentHurdleUser.count = mockCount; + + // const mockRescusalsCount = jest.fn(); + // mockRescusalsCount.mockReturnValue(0); + // ApplicantRecusals.count = mockRescusalsCount; + + // const mockExisting = jest.fn(); + // mockExisting.mockReturnValue(undefined); + // ApplicationAssignments.findOne = mockExisting; + + // const findAllMock = jest.fn(); + // findAllMock.mockReturnValue([]); + // ApplicantStatusMetricsAgg.findAll = findAllMock; + + // const findAllApplicantsMock = jest.fn(); + // findAllApplicantsMock.mockReturnValue([]); + // Applicant.findAll = findAllApplicantsMock; + + // const findallAssignmentsMock = jest.fn(); + // findallAssignmentsMock.mockReturnValue([]); + // ApplicationAssignments.findAll = findallAssignmentsMock; + + // const svc = new ApplicationAssignmentService(); + // const rst = await svc.nextQueue('2e2549c8-5282-4d78-8fce-e64d8a17807f', '9521ab6d-969c-4cbe-9270-c4ec5182b09a'); + // expect(rst).toBeNull(); + // }); + // it('works and returns valid assignment', async () => { + // const mockCount = jest.fn(); + // mockCount.mockReturnValue(1); + + // AssessmentHurdleUser.count = mockCount; + + // const mockRescusalsCount = jest.fn(); + // mockRescusalsCount.mockReturnValue(0); + // ApplicantRecusals.count = mockRescusalsCount; + + // const mockExisting = jest.fn(); + // mockExisting.mockReturnValue(undefined); + // ApplicationAssignments.findOne = mockExisting; + + // const findAllMock = jest.fn(); + + // const assignment: ApplicantStatusMetricsAggAttributes = { + // applicant_id: 'c13c726e-bb4b-4c09-9c1f-86ada96c4b8a', + // amendment_count: 0, + // completed_count: 0, + // pending_evaluation_count: 1, + // review_count: 0, + // }; + // findAllMock.mockReturnValue([assignment]); + + // ApplicantStatusMetricsAgg.findAll = findAllMock; + // const findAllApplicantsMock = jest.fn(); + // findAllApplicantsMock.mockReturnValue([]); + // Applicant.findAll = findAllApplicantsMock; + + // const findallAssignmentsMock = jest.fn(); + // findallAssignmentsMock.mockReturnValue([]); + // ApplicationAssignments.findAll = findallAssignmentsMock; + + // const mockCreate = jest.fn(); + // const created: ApplicationAssignmentsAttributes = { + // applicant_id: 'c13c726e-bb4b-4c09-9c1f-86ada96c4b8a', + // assessment_hurdle_id: 'd669f94a-7170-4cc0-b9c2-d05b328c603b', + // evaluator_id: 'a3538f22-71a8-44e8-b789-5ec330798ad2', + // id: '820be164-562f-4dfa-8ce0-fd68f787d80f', + // active: true, + // created_at: new Date(), + // updated_at: new Date(), + // }; + // mockCreate.mockReturnValue(created); + // ApplicationAssignments.create = mockCreate; + + // const svc = new ApplicationAssignmentService(); + // const rst = await svc.nextQueue('d669f94a-7170-4cc0-b9c2-d05b328c603b', 'a3538f22-71a8-44e8-b789-5ec330798ad2'); + // expect(rst).not.toBeNull(); + // expect(rst!.applicant_id).toEqual('820be164-562f-4dfa-8ce0-fd68f787d80f'); + // }); + + // it('Recused Evaluator+Applicant combo does not return assignement', async () => { + // const mockCount = jest.fn(); + // mockCount.mockReturnValue(1); + + // AssessmentHurdleUser.count = mockCount; + + // const mockRescusalsCount = jest.fn(); + // mockRescusalsCount.mockImplementation(obj => { + // return obj.where.applicant_id === 'c13c726e-bb4b-4c09-9c1f-86ada96c4b8a' ? 1 : 0; + // }); + // ApplicantRecusals.count = mockRescusalsCount; + + // const mockExisting = jest.fn(); + // mockExisting.mockReturnValue(undefined); + // ApplicationAssignments.findOne = mockExisting; + + // const findAllMock = jest.fn(); + + // const assignment: ApplicantStatusMetricsAggAttributes = { + // applicant_id: 'c13c726e-bb4b-4c09-9c1f-86ada96c4b8a', + // amendment_count: 0, + // completed_count: 0, + // pending_evaluation_count: 1, + // review_count: 0, + // }; + // const assignment1: ApplicantStatusMetricsAggAttributes = { + // applicant_id: '800a37ae-9bab-4897-a4f5-7b3a412e96d6', + // amendment_count: 0, + // completed_count: 0, + // pending_evaluation_count: 1, + // review_count: 0, + // }; + // findAllMock.mockReturnValue([assignment, assignment1]); + + // ApplicantStatusMetricsAgg.findAll = findAllMock; + + // const findAllFlaggedApplicantsMock = jest.fn(); + // findAllFlaggedApplicantsMock.mockReturnValue([]); + // Applicant.findAll = findAllFlaggedApplicantsMock; + + // const findallAssignmentsMock = jest.fn(); + // findallAssignmentsMock.mockReturnValue(['c13c726e-bb4b-4c09-9c1f-86ada96c4b8a', '800a37ae-9bab-4897-a4f5-7b3a412e96d6']); + // ApplicationAssignments.findAll = findallAssignmentsMock; + + // const mockCreate = jest.fn(); + // mockCreate.mockImplementation(obj => { + // expect(obj.applicant_id).toEqual('800a37ae-9bab-4897-a4f5-7b3a412e96d6'); + // const created: ApplicationAssignmentsAttributes = { + // applicant_id: obj.applicant_id, + // assessment_hurdle_id: 'd669f94a-7170-4cc0-b9c2-d05b328c603b', + // evaluator_id: 'a3538f22-71a8-44e8-b789-5ec330798ad2', + // id: '820be164-562f-4dfa-8ce0-fd68f787d80f', + // active: true, + // created_at: new Date(), + // updated_at: new Date(), + // }; + // return created; + // }); + + // ApplicationAssignments.create = mockCreate; + + // const svc = new ApplicationAssignmentService(); + // const rst = await svc.nextQueue('d669f94a-7170-4cc0-b9c2-d05b328c603b', 'a3538f22-71a8-44e8-b789-5ec330798ad2'); + // expect(rst).not.toBeNull(); + // expect(rst!.applicant_id).toEqual('820be164-562f-4dfa-8ce0-fd68f787d80f'); + // }); + + // it('Flagged applicant should not be returned', async () => { + // const mockCount = jest.fn(); + // mockCount.mockReturnValue(1); + + // AssessmentHurdleUser.count = mockCount; + + // const mockRescusalsCount = jest.fn(); + // mockRescusalsCount.mockReturnValue(0); + // ApplicantRecusals.count = mockRescusalsCount; + + // const mockExisting = jest.fn(); + // mockExisting.mockReturnValue(undefined); + // ApplicationAssignments.findOne = mockExisting; + + // const findAllMock = jest.fn(); + + // const assignment: ApplicantStatusMetricsAggAttributes = { + // applicant_id: 'c13c726e-bb4b-4c09-9c1f-86ada96c4b8a', + // amendment_count: 0, + // completed_count: 0, + // pending_evaluation_count: 1, + // review_count: 0, + // }; + // findAllMock.mockReturnValue([assignment]); + + // ApplicantStatusMetricsAgg.findAll = findAllMock; + + // const findAllApplicantsMock = jest.fn(); + // const flaggedApplicant: ApplicantAttributes = { + // id: '040d2ee4-19f6-4730-9c72-e479af131178', + // flag_type: 1, + // flag_message: 'Flagged', + // assessment_hurdle_id: 'd669f94a-7170-4cc0-b9c2-d05b328c603b', + // }; + // findAllApplicantsMock.mockReturnValue([flaggedApplicant]); + // Applicant.findAll = findAllApplicantsMock; + + // const findallAssignmentsMock = jest.fn(); + // findallAssignmentsMock.mockReturnValue([]); + // ApplicationAssignments.findAll = findallAssignmentsMock; + + // const mockCreate = jest.fn(); + + // mockCreate.mockImplementation(obj => { + // expect(obj.applicant_id).toEqual('c13c726e-bb4b-4c09-9c1f-86ada96c4b8a'); + // const created: ApplicationAssignmentsAttributes = { + // applicant_id: 'c13c726e-bb4b-4c09-9c1f-86ada96c4b8a', + // assessment_hurdle_id: 'd669f94a-7170-4cc0-b9c2-d05b328c603b', + // evaluator_id: 'a3538f22-71a8-44e8-b789-5ec330798ad2', + // id: '820be164-562f-4dfa-8ce0-fd68f787d80f', + // active: true, + // created_at: new Date(), + // updated_at: new Date(), + // }; + // return created; + // }); + // ApplicationAssignments.create = mockCreate; + + // const svc = new ApplicationAssignmentService(); + // const rst = await svc.nextQueue('d669f94a-7170-4cc0-b9c2-d05b328c603b', 'a3538f22-71a8-44e8-b789-5ec330798ad2'); + // expect(rst).not.toBeNull(); + // expect(rst!.applicant_id).toEqual('820be164-562f-4dfa-8ce0-fd68f787d80f'); + // }); +}); diff --git a/api/src/tests/services.tests/assessmenthurdle.service.spec.ts b/api/src/tests/services.tests/assessmenthurdle.service.spec.ts new file mode 100644 index 0000000..b6a42a0 --- /dev/null +++ b/api/src/tests/services.tests/assessmenthurdle.service.spec.ts @@ -0,0 +1,177 @@ +import CreateAssessmentHurdleDto from '../../dto/createassessmenthurdle.dto'; +import HttpException from '../../exceptions/HttpException'; +import { AssessmentHurdle } from '../../models/assessment_hurdle'; +import { AssessmentHurdleMeta } from '../../models/assessment_hurdle_meta'; +import AssessmentHurdleService from '../../services/assessmenthurdle.service'; + +jest.mock('../../models/assessment_hurdle'); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('getBy Tests', () => { + it('getById returns value', async () => { + const mockFindByPk = jest.fn(); + mockFindByPk.mockReturnValue({ + id: 'ff844ae0-22ad-47f0-af8f-8e1979cf0483', + department_name: 'body.departmentName', + component_name: 'body.componentName', + position_name: 'body.positionName', + position_details: 'body.positionDetails', + locations: 'body.locations', + start_datetime: new Date(), + end_datetime: new Date(), + hurdle_display_type: 1, + evaluations_required: 1000, + hr_name: 'body.hrName', + hr_email: 'body.hrEmail', + }); + AssessmentHurdle.findByPk = mockFindByPk; + }); + it('getById returns 400 for empty Id', async () => { + const svc = new AssessmentHurdleService(); + await expect(svc.getById('')).rejects.toThrowError(HttpException); + }); + it('getById returns 404 for not found', async () => { + const mockFindByPk = jest.fn(); + mockFindByPk.mockReturnValue(undefined); + AssessmentHurdle.findByPk = mockFindByPk; + const svc = new AssessmentHurdleService(); + await expect(svc.getById('a0490dd1-1933-4148-9d6e-1f3b551d6361')).rejects.toThrowError(HttpException); + }); +}); + +describe('upsert Tests', () => { + it('upsert creates item and meta', async () => { + const mockUpsert = jest.fn(); + mockUpsert.mockReturnValue([ + { + id: 'ff844ae0-22ad-47f0-af8f-8e1979cf0483', + department_name: 'body.departmentName', + component_name: 'body.componentName', + position_name: 'body.positionName', + position_details: 'body.positionDetails', + locations: 'body.locations', + start_datetime: new Date(), + end_datetime: new Date(), + hurdle_display_type: 1, + evaluations_required: 1000, + hr_name: 'body.hrName', + hr_email: 'body.hrEmail', + }, + true, + ]); + AssessmentHurdle.upsert = mockUpsert; + const mockMetaUpsert = jest.fn(); + mockMetaUpsert.mockReturnValue([ + { + id: 'fa30e581-4140-4267-bc85-e71ee7bc36cc', + staffing_assessment_id: 'body.assessmentId', + staffing_vacancy_id: 'body.vacancyId', + assessment_hurdle_id: 'ff844ae0-22ad-47f0-af8f-8e1979cf0483', + staffing_fail_nor: 'body.failNor', + staffing_pass_nor: 'body.passNor', + created_at: new Date(), + updated_at: new Date(), + }, + true, + ]); + AssessmentHurdleMeta.findOrCreate = mockMetaUpsert; + + const mockUpdate = jest.fn(); + AssessmentHurdleMeta.update = mockUpdate; + const svc = new AssessmentHurdleService(); + const body: CreateAssessmentHurdleDto = { + existingId: undefined, + assessmentId: 'assessmentId', + evaluationsRequired: 2, + componentName: 'compenentName', + departmentName: 'deparmentName', + endDatetime: new Date(), + startDatetime: new Date(), + failNor: 'FAIL', + passNor: 'PASS', + hrEmail: 'hr@email', + hrName: 'hr name', + hurdleDisplayType: 1, + locations: 'D.C.', + positionDetails: '', + positionName: 'positionName', + vacancyId: 'vacancyId', + assessmentName: 'AmazingName1', + }; + const rst = await svc.upsert(body); + expect(mockUpsert).toBeCalledTimes(1); + expect(mockMetaUpsert).toBeCalledTimes(1); + expect(mockUpdate).toBeCalledTimes(0); + expect(rst.id).toEqual('ff844ae0-22ad-47f0-af8f-8e1979cf0483'); + }); + it('upsert updates existing meta', async () => { + const mockUpsert = jest.fn(); + mockUpsert.mockReturnValue([ + { + id: 'ff844ae0-22ad-47f0-af8f-8e1979cf0483', + department_name: 'body.departmentName', + component_name: 'body.componentName', + position_name: 'body.positionName', + position_details: 'body.positionDetails', + locations: 'body.locations', + start_datetime: new Date(), + end_datetime: new Date(), + hurdle_display_type: 1, + evaluations_required: 1000, + hr_name: 'body.hrName', + hr_email: 'body.hrEmail', + }, + true, + ]); + AssessmentHurdle.upsert = mockUpsert; + const mockUpdate = jest.fn(); + mockUpdate.mockReturnValue(true); + + const mockMetaUpsert = jest.fn(); + mockMetaUpsert.mockReturnValue([ + { + id: 'fa30e581-4140-4267-bc85-e71ee7bc36cc', + staffing_assessment_id: 'body.assessmentId', + staffing_vacancy_id: 'body.vacancyId', + assessment_hurdle_id: 'ff844ae0-22ad-47f0-af8f-8e1979cf0483', + staffing_fail_nor: 'body.failNor', + staffing_pass_nor: 'body.passNor', + created_at: new Date(), + updated_at: new Date(), + update: mockUpdate, + }, + false, + ]); + AssessmentHurdleMeta.findOrCreate = mockMetaUpsert; + + AssessmentHurdleMeta.update = mockUpdate; + const svc = new AssessmentHurdleService(); + const body: CreateAssessmentHurdleDto = { + existingId: undefined, + assessmentId: 'assessmentId', + evaluationsRequired: 2, + componentName: 'compenentName', + departmentName: 'deparmentName', + endDatetime: new Date(), + startDatetime: new Date(), + failNor: 'FAIL', + passNor: 'PASS', + hrEmail: 'hr@email', + hrName: 'hr name', + hurdleDisplayType: 1, + locations: 'D.C.', + positionDetails: '', + positionName: 'positionName', + vacancyId: 'vacancyId', + assessmentName: 'SomeGreatName', + }; + const rst = await svc.upsert(body); + expect(mockUpsert).toBeCalledTimes(1); + expect(mockMetaUpsert).toBeCalledTimes(1); + expect(mockUpdate).toBeCalledTimes(1); + expect(rst.id).toEqual('ff844ae0-22ad-47f0-af8f-8e1979cf0483'); + }); +}); diff --git a/api/src/tests/services.tests/competency.service.spec.ts b/api/src/tests/services.tests/competency.service.spec.ts new file mode 100644 index 0000000..2ad7b3e --- /dev/null +++ b/api/src/tests/services.tests/competency.service.spec.ts @@ -0,0 +1,236 @@ +import HttpException from '../../exceptions/HttpException'; +import { Competency } from '../../models/competency'; +import CompetencyService from '../../services/competency.service'; +import { SpecialtyCompetencies } from '../../models/specialty_competencies'; +import { CompetencySelectors } from '../../models/competency_selectors'; +import CreateCompetencyDto from '../../dto/createcompetency.dto'; + +jest.mock('../../models/competency'); +jest.mock('../../models/specialty_competencies'); +jest.mock('../../models/competency_selectors'); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('getBy Tests', () => { + it('getById returns value', async () => { + const mockFindByPk = jest.fn(); + mockFindByPk.mockReturnValue({ + id: 'cea8afa4-869c-49a6-b0ce-3bf52d21ea86', + name: 'string', + definition: 'string', + required_proficiency_definition: 'string', + display_type: 0, + screen_out: false, + created_at: new Date(), + updated_at: new Date(), + }); + Competency.findByPk = mockFindByPk; + const svc = new CompetencyService(); + const rst = await svc.getById('cea8afa4-869c-49a6-b0ce-3bf52d21ea86'); + expect(mockFindByPk).toBeCalledTimes(1); + expect(rst.id).toEqual('cea8afa4-869c-49a6-b0ce-3bf52d21ea86'); + }); + it('getById returns 400 for empty value', async () => { + const svc = new CompetencyService(); + await expect(svc.getById('')).rejects.toThrowError(HttpException); + }); + it('getById returns 404 for not found value', async () => { + const mockFindByPk = jest.fn(); + mockFindByPk.mockReturnValue(undefined); + Competency.findByPk = mockFindByPk; + const svc = new CompetencyService(); + await expect(svc.getById('')).rejects.toThrowError(HttpException); + }); +}); + +describe('mapping to Specialty Tests', () => { + it('getAllMappingsById returns value', async () => { + const mockFindAll = jest.fn(); + mockFindAll.mockReturnValue([ + { + id: 'c271341e-1c2e-4395-a43e-7de7990a0e53', + specialty_id: '12157d0e-1e8f-4023-b4d6-87152620f747', + competency_id: 'b9b8decb-9326-401c-9696-ab2e7205022f', + created_at: new Date(), + updated_at: new Date(), + }, + ]); + SpecialtyCompetencies.findAll = mockFindAll; + const svc = new CompetencyService(); + const rst = await svc.getAllMappingsById('b9b8decb-9326-401c-9696-ab2e7205022f'); + expect(mockFindAll).toBeCalledTimes(1); + expect(rst.length).toEqual(1); + }); + it('getAllMappingsById returns 400 for empty Value', async () => { + const svc = new CompetencyService(); + await expect(svc.getAllMappingsById('')).rejects.toThrowError(HttpException); + }); + it('getallMappingsById returns 404 for not found', async () => { + const mockFindAll: jest.Mock = jest.fn(); + mockFindAll.mockReturnValue(undefined); + const svc = new CompetencyService(); + await expect(svc.getAllMappingsById('b9b8decb-9326-401c-9696-ab2e7205022f')).rejects.toThrowError(HttpException); + }); + it('upsert creates Record and mappings', async () => { + const assessmentHurdleId = 'b06834f1-e9a0-48d4-81a8-a65967e487f7'; + const mockUpsert = jest.fn(); + mockUpsert.mockReturnValue([ + { + id: '026dca8d-6246-4ba4-ac4a-6bec5116a5be', + definition: 'Something', + name: 'Competency', + required_proficiency_definition: 'yep', + display_type: 1, + screen_out: false, + }, + true, + ]); + + Competency.upsert = mockUpsert; + + const mockUpsertSelectors = jest.fn(); + mockUpsertSelectors.mockReturnValue({ + id: 'dc5a6c49-7657-4330-b3bc-334672df75c8', + competency_id: '026dca8d-6246-4ba4-ac4a-6bec5116a5be', + display_name: 'Meets Selector', + point_value: 1000, + default_text: 'Yep', + sort_order: 1, + created_at: new Date(), + updated_at: new Date(), + }); + + CompetencySelectors.create = mockUpsertSelectors; + + const svc = new CompetencyService(); + const body: CreateCompetencyDto = { + existingId: undefined, + name: 'yep', + localId: 'localId', + definition: 'definition', + displayType: 1, + requiredProficiencyDefinition: 'rpd', + screenOut: false, + assessmentHurdleId, + selectors: [ + { + defaultText: 'defaultText', + displayName: 'displayName', + pointValue: 1000, + sortOrder: 1, + existingId: undefined, + competencyId: '', + }, + ], + }; + const rst = await svc.upsert(body, assessmentHurdleId); + expect(mockUpsert).toBeCalledTimes(1); + expect(mockUpsertSelectors).toBeCalledTimes(1); + expect(rst.id).toEqual('026dca8d-6246-4ba4-ac4a-6bec5116a5be'); + }); + it('upsert does not create mapping without specialtyId', async () => { + const mockUpsert = jest.fn(); + mockUpsert.mockReturnValue([ + { + id: '026dca8d-6246-4ba4-ac4a-6bec5116a5be', + definition: 'Something', + name: 'Competency', + required_proficiency_definition: 'yep', + display_type: 1, + screen_out: false, + }, + true, + ]); + + Competency.upsert = mockUpsert; + + const mockUpsertSelectors = jest.fn(); + mockUpsertSelectors.mockReturnValue({ + id: 'dc5a6c49-7657-4330-b3bc-334672df75c8', + competency_id: '026dca8d-6246-4ba4-ac4a-6bec5116a5be', + display_name: 'Meets Selector', + point_value: 1000, + default_text: 'Yep', + sort_order: 1, + created_at: new Date(), + updated_at: new Date(), + }); + + CompetencySelectors.create = mockUpsertSelectors; + const svc = new CompetencyService(); + const assessmentHurdleId = 'b06834f1-e9a0-48d4-81a8-a65967e487f7'; + const body: CreateCompetencyDto = { + existingId: undefined, + localId: 'localId', + name: 'yep', + definition: 'definition', + assessmentHurdleId, + displayType: 1, + requiredProficiencyDefinition: 'rpd', + screenOut: false, + selectors: [ + { + defaultText: 'defaultText', + displayName: 'displayName', + pointValue: 1000, + sortOrder: 1, + existingId: undefined, + competencyId: '', + }, + ], + }; + const rst = await svc.upsert(body, assessmentHurdleId); + expect(mockUpsert).toBeCalledTimes(1); + expect(mockUpsertSelectors).toBeCalledTimes(1); + expect(rst.id).toEqual('026dca8d-6246-4ba4-ac4a-6bec5116a5be'); + }); + it('upsert does not create selectors without selectors', async () => { + const mockUpsert = jest.fn(); + mockUpsert.mockReturnValue([ + { + id: '026dca8d-6246-4ba4-ac4a-6bec5116a5be', + definition: 'Something', + name: 'Competency', + required_proficiency_definition: 'yep', + display_type: 1, + screen_out: false, + }, + true, + ]); + + Competency.upsert = mockUpsert; + + const mockUpsertSelectors = jest.fn(); + mockUpsertSelectors.mockReturnValue({ + id: 'dc5a6c49-7657-4330-b3bc-334672df75c8', + competency_id: '026dca8d-6246-4ba4-ac4a-6bec5116a5be', + display_name: 'Meets Selector', + point_value: 1000, + default_text: 'Yep', + sort_order: 1, + created_at: new Date(), + updated_at: new Date(), + }); + + CompetencySelectors.create = mockUpsertSelectors; + const svc = new CompetencyService(); + const assessmentHurdleId = 'b06834f1-e9a0-48d4-81a8-a65967e487f7'; + const body: CreateCompetencyDto = { + existingId: undefined, + localId: 'localId', + name: 'yep', + definition: 'definition', + assessmentHurdleId, + displayType: 1, + requiredProficiencyDefinition: 'rpd', + screenOut: false, + selectors: [], + }; + const rst = await svc.upsert(body, assessmentHurdleId); + expect(mockUpsert).toBeCalledTimes(1); + expect(mockUpsertSelectors).toBeCalledTimes(0); + expect(rst.id).toEqual('026dca8d-6246-4ba4-ac4a-6bec5116a5be'); + }); +}); diff --git a/api/src/tests/services.tests/evaluation.service.spec.ts b/api/src/tests/services.tests/evaluation.service.spec.ts new file mode 100644 index 0000000..e7a41b3 --- /dev/null +++ b/api/src/tests/services.tests/evaluation.service.spec.ts @@ -0,0 +1,762 @@ +process.env.NODE_ENV = 'testing'; +process.env.APP_ENV = 'testing'; + +import { EvaluationApplicationReviewSubmitDto } from '../../dto/evaluationreviewsubmit.dto'; +import EvaluationSubmitDto from '../../dto/evaluationsubmit.dto'; +import HttpException from '../../exceptions/HttpException'; +import { Applicant } from '../../models/applicant'; +import { AssessmentHurdleUser } from '../../models/assessment_hurdle_user'; +import EvaluationService from '../../services/evaluation.service'; +import { EvaluationMocks } from './evaluationmocks'; +import MockDB from '../mockDB'; + +//#region jest mock +jest.mock('../../models/application'); +jest.mock('../../models/application_assignments'); +jest.mock('../../models/application_evaluation'); +jest.mock('../../models/application_evaluation_competency'); +jest.mock('../../models/applicant_evaluation_feedback'); +jest.mock('../../models/competency'); +jest.mock('../../models/competency_evaluation'); +jest.mock('../../models/competency_selectors'); +jest.mock('../../models/specialty'); +jest.mock('../../models/specialty_competencies'); +jest.mock('../../models/applicant'); +jest.mock('../../database'); +//#endregion + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const dbInstance = new MockDB(); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('submitEvaluation Tests', () => { + it('full submission works', async () => { + const assessmentHurdleId = 'ec12d26a-9529-4b2e-a006-a57465af1f3e'; + const applicantId = 'ea2092bf-a290-4695-bd1e-cbbe442c621e'; + const evaluatorId = 'fc81850e-1bbd-4e28-98ed-7d91dfe86b38'; + const body: EvaluationSubmitDto = { + note: 'Experience is good.', + evaluatorId, + competencyEvals: [ + { + competencyId: '6fa17c61-c995-4776-9450-43e780ce4869', + selectorId: 'dcb4e5cf-0b8a-41a4-94dd-fda5c74e4755', + note: 'passed with flying colors', + }, + ], + }; + const applications = [ + { + id: 'd84f9a55-80b1-445e-a52b-b98d396de520', + applicant_id: 'ea2092bf-a290-4695-bd1e-cbbe442c621e', + specialty_id: 'a256a733-ba09-446c-a3c7-59ca19e09e43', + }, + ]; + const compSelectors = [ + { + id: 'dcb4e5cf-0b8a-41a4-94dd-fda5c74e4755', + competency_id: '6fa17c61-c995-4776-9450-43e780ce4869', + default_text: 'Default Text', + display_name: 'Selector Default', + point_value: 20, + sort_order: 1, + }, + ]; + const competency = { + id: '6fa17c61-c995-4776-9450-43e780ce4869', + local_id: 'testComp1', + name: 'Test Comp 1', + definition: ' Ye[p', + assessment_hurdle_id: assessmentHurdleId, + display_type: 1, + required_proficiency_definition: 'L33t', + screen_out: false, + }; + + const mocker = new EvaluationMocks() + .setCompetencyReturns([competency]) + .setTempApplicant() + .setApplicationsReturn( + applications, + { + id: 'a256a733-ba09-446c-a3c7-59ca19e09e43', + local_id: 'testSpec1', + name: 'Test Spec 1', + assessment_hurdle_id: assessmentHurdleId, + points_required: 20, + }, + [ + { + id: '3e0b841f-09f3-433b-9403-024ca1fe0fb3', + competency_id: '6fa17c61-c995-4776-9450-43e780ce4869', + specialty_id: 'a256a733-ba09-446c-a3c7-59ca19e09e43', + }, + ], + ) + .setCompetencySelectorsReturn(compSelectors); + mocker + .setApplicationEvaluationReturnOf1(applications[0].id) + .setCompetencyEvalutionsReturnOf1(competency.id, compSelectors[0].id) + .setApplicationEvalComptencyReturn({ + id: 'c4f1e8ab-daa3-4e1a-8c86-88e7cfaeceaf', + application_evaluation_id: 'd542b471-6ad9-42f4-8fbd-334eab7e34e7', + competency_evaluation_id: '5ea8b22d-d61d-44bd-95af-cf53ca05b648', + }) + .setApplicationAssignmentReturn([ + { + applicant_id: 'ea2092bf-a290-4695-bd1e-cbbe442c621e', + evaluator_id: '044bb051-2de5-4bca-a3ab-d5653b772995', + id: 'ec3c98fd-3d8c-4802-97e2-18e85215dc79', + active: false, + assessment_hurdle_id: assessmentHurdleId, + }, + ]); + + const svc = new EvaluationService(); + + const [appEval, compEval] = await svc.submitEvaluation(dbInstance, body, assessmentHurdleId, applicantId, evaluatorId); + expect(appEval.length).toEqual(1); + expect(compEval.length).toEqual(1); + }); + + it('full submission creates correct number of evaluations', async () => { + const applicantId = 'ea2092bf-a290-4695-bd1e-cbbe442c621e'; + const evaluatorId = 'fc81850e-1bbd-4e28-98ed-7d91dfe86b38'; + const assessmentHurdleId = 'ec12d26a-9529-4b2e-a006-a57465af1f3e'; + const body: EvaluationSubmitDto = { + note: 'Experience is good.', + evaluatorId, + competencyEvals: [ + { + competencyId: '6fa17c61-c995-4776-9450-43e780ce4869', + selectorId: 'dcb4e5cf-0b8a-41a4-94dd-fda5c74e4755', + note: 'passed with flying colors', + }, + ], + }; + const applications = [ + { + id: 'd84f9a55-80b1-445e-a52b-b98d396de520', + applicant_id: 'ea2092bf-a290-4695-bd1e-cbbe442c621e', + specialty_id: 'a256a733-ba09-446c-a3c7-59ca19e09e43', + }, + { + id: '3b6b18ec-bd1c-4d4c-ba93-d93c1146dc83', + applicant_id: 'ea2092bf-a290-4695-bd1e-cbbe442c621e', + specialty_id: 'a256a733-ba09-446c-a3c7-59ca19e09e43', + }, + ]; + const compSelectors = [ + { + id: 'dcb4e5cf-0b8a-41a4-94dd-fda5c74e4755', + competency_id: '6fa17c61-c995-4776-9450-43e780ce4869', + default_text: 'Default Text', + display_name: 'Selector Default', + point_value: 20, + sort_order: 1, + }, + ]; + const competency = { + id: '6fa17c61-c995-4776-9450-43e780ce4869', + local_id: 'testComp1', + name: 'Test Comp 1', + definition: ' Ye[p', + assessment_hurdle_id: assessmentHurdleId, + display_type: 1, + required_proficiency_definition: 'L33t', + screen_out: false, + }; + + const mocker = new EvaluationMocks() + .setCompetencyReturns([competency]) + .setTempApplicant() + .setApplicationsReturn( + applications, + { + id: 'a256a733-ba09-446c-a3c7-59ca19e09e43', + local_id: 'testSpec1', + name: 'Test Spec 1', + assessment_hurdle_id: assessmentHurdleId, + points_required: 20, + }, + [ + { + id: '3e0b841f-09f3-433b-9403-024ca1fe0fb3', + competency_id: '6fa17c61-c995-4776-9450-43e780ce4869', + specialty_id: 'a256a733-ba09-446c-a3c7-59ca19e09e43', + }, + ], + ) + .setCompetencySelectorsReturn(compSelectors); + mocker + .setApplicationEvaluationPassthroughReturn() + .setCompetencyEvalutionsReturnOf1(competency.id, compSelectors[0].id) + .setApplicationEvalComptencyReturn({ + id: 'c4f1e8ab-daa3-4e1a-8c86-88e7cfaeceaf', + application_evaluation_id: 'd542b471-6ad9-42f4-8fbd-334eab7e34e7', + competency_evaluation_id: '5ea8b22d-d61d-44bd-95af-cf53ca05b648', + }) + .setApplicationAssignmentReturn([ + { + applicant_id: 'ea2092bf-a290-4695-bd1e-cbbe442c621e', + evaluator_id: '044bb051-2de5-4bca-a3ab-d5653b772995', + id: 'ec3c98fd-3d8c-4802-97e2-18e85215dc79', + active: false, + assessment_hurdle_id: assessmentHurdleId, + }, + ]); + + const svc = new EvaluationService(); + const [appEval, compEval] = await svc.submitEvaluation(dbInstance, body, assessmentHurdleId, applicantId, evaluatorId); + + expect(appEval.length).toEqual(2); + expect(compEval.length).toEqual(1); + mocker.expectMockToBeCalled(EvaluationMocks.MockName.applications); + mocker.expectMockToBeCalled(EvaluationMocks.MockName.competencySelectors); + mocker.expectMockToBeCalled(EvaluationMocks.MockName.applicationEvaluations); + mocker.expectMockToBeCalled(EvaluationMocks.MockName.applicationEvaluationComps); + }); + + it('competency counts off should throw 400', async () => { + const applicantId = 'ea2092bf-a290-4695-bd1e-cbbe442c621e'; + const evaluatorId = 'fc81850e-1bbd-4e28-98ed-7d91dfe86b38'; + const assessmentHurdleId = 'ec12d26a-9529-4b2e-a006-a57465af1f3e'; + const body: EvaluationSubmitDto = { + note: 'Experience is good.', + evaluatorId, + competencyEvals: [ + { + competencyId: '6fa17c61-c995-4776-9450-43e780ce4869', + selectorId: 'dcb4e5cf-0b8a-41a4-94dd-fda5c74e4755', + note: 'passed with flying colors', + }, + ], + }; + const applications = [ + { + id: 'd84f9a55-80b1-445e-a52b-b98d396de520', + applicant_id: 'ea2092bf-a290-4695-bd1e-cbbe442c621e', + specialty_id: 'a256a733-ba09-446c-a3c7-59ca19e09e43', + }, + ]; + const compSelectors = [ + { + id: 'dcb4e5cf-0b8a-41a4-94dd-fda5c74e4755', + competency_id: '6fa17c61-c995-4776-9450-43e780ce4869', + default_text: 'Default Text', + display_name: 'Selector Default', + point_value: 20, + sort_order: 1, + }, + { + id: '376eb971-0bc3-4251-80ce-33b4a434f124', + competency_id: '716c9aac-c2ff-4436-b270-ea3f453d6fdd', + default_text: 'Default Text 2', + display_name: 'Selector Default 2', + point_value: 10, + sort_order: 2, + }, + ]; + + const competency = { + id: '6fa17c61-c995-4776-9450-43e780ce4869', + local_id: 'testComp1', + name: 'Test Comp 1', + definition: ' Ye[p', + assessment_hurdle_id: assessmentHurdleId, + display_type: 1, + required_proficiency_definition: 'L33t', + screen_out: false, + }; + + const mocker = new EvaluationMocks() + .setCompetencyReturns([competency]) + .setTempApplicant() + .setApplicationsReturn( + applications, + { + id: 'a256a733-ba09-446c-a3c7-59ca19e09e43', + local_id: 'testSpec1', + name: 'Test Spec 1', + assessment_hurdle_id: assessmentHurdleId, + points_required: 20, + }, + [ + { + id: '3e0b841f-09f3-433b-9403-024ca1fe0fb3', + competency_id: '6fa17c61-c995-4776-9450-43e780ce4869', + specialty_id: 'a256a733-ba09-446c-a3c7-59ca19e09e43', + }, + { + id: '3cb28109-222f-4e8a-9129-1001914c17b6', + competency_id: '716c9aac-c2ff-4436-b270-ea3f453d6fdd', + specialty_id: 'a256a733-ba09-446c-a3c7-59ca19e09e43', + }, + ], + ) + .setCompetencySelectorsReturn(compSelectors); + mocker + .setApplicationEvaluationReturnOf1(applications[0].id) + .setCompetencyEvalutionsReturnOf1(competency.id, compSelectors[0].id) + .setApplicationEvalComptencyReturn({ + id: 'c4f1e8ab-daa3-4e1a-8c86-88e7cfaeceaf', + application_evaluation_id: 'd542b471-6ad9-42f4-8fbd-334eab7e34e7', + competency_evaluation_id: '5ea8b22d-d61d-44bd-95af-cf53ca05b648', + }) + .setApplicationAssignmentReturn([ + { + applicant_id: 'ea2092bf-a290-4695-bd1e-cbbe442c621e', + evaluator_id: '044bb051-2de5-4bca-a3ab-d5653b772995', + id: 'ec3c98fd-3d8c-4802-97e2-18e85215dc79', + active: false, + assessment_hurdle_id: assessmentHurdleId, + }, + ]); + const svc = new EvaluationService(); + await expect(svc.submitEvaluation(dbInstance, body, assessmentHurdleId, applicantId, evaluatorId)).rejects.toThrowError(HttpException); + mocker.expectMockToBeCalled(EvaluationMocks.MockName.applications); + mocker.expectMockToBeCalled(EvaluationMocks.MockName.competencySelectors); + mocker.expectMockToBeCalled(EvaluationMocks.MockName.applicationEvaluations, 0); + }); + it('missing competency should throw 400', async () => { + const applicantId = 'ea2092bf-a290-4695-bd1e-cbbe442c621e'; + const assessmentHurdleId = 'ec12d26a-9529-4b2e-a006-a57465af1f3e'; + const evaluatorId = 'fc81850e-1bbd-4e28-98ed-7d91dfe86b38'; + const body: EvaluationSubmitDto = { + note: 'Experience is good.', + evaluatorId, + competencyEvals: [ + { + competencyId: '6fa17c61-c995-4776-9450-43e780ce4869', + selectorId: 'dcb4e5cf-0b8a-41a4-94dd-fda5c74e4755', + note: 'passed with flying colors', + }, + { + competencyId: '716c9aac-c2ff-4436-b270-ea3f453d6fdd', + selectorId: '376eb971-0bc3-4251-80ce-33b4a434f124', + note: 'passed with flying colors', + }, + ], + }; + const applications = [ + { + id: 'd84f9a55-80b1-445e-a52b-b98d396de520', + applicant_id: 'ea2092bf-a290-4695-bd1e-cbbe442c621e', + specialty_id: 'a256a733-ba09-446c-a3c7-59ca19e09e43', + }, + ]; + const compSelectors = [ + { + id: 'dcb4e5cf-0b8a-41a4-94dd-fda5c74e4755', + competency_id: '6fa17c61-c995-4776-9450-43e780ce4869', + default_text: 'Default Text', + display_name: 'Selector Default', + point_value: 20, + sort_order: 1, + }, + { + id: '376eb971-0bc3-4251-80ce-33b4a434f124', + competency_id: '716c9aac-c2ff-4436-b270-ea3f453d6fdd', + default_text: 'Default Text 2', + display_name: 'Selector Default 2', + point_value: 10, + sort_order: 2, + }, + { + id: 'e871eb61-2465-41ff-92f4-2c11f4135ff2', + competency_id: 'b7931ade-0d1a-4800-9dea-b6f3b1fc1ab6', + default_text: 'Default Text 3', + display_name: 'Selector Default 3', + point_value: 11, + sort_order: 3, + }, + ]; + + const competency = { + id: '6fa17c61-c995-4776-9450-43e780ce4869', + local_id: 'testComp1', + name: 'Test Comp 1', + definition: ' Ye[p', + assessment_hurdle_id: assessmentHurdleId, + display_type: 1, + required_proficiency_definition: 'L33t', + screen_out: false, + }; + + const mocker = new EvaluationMocks() + .setCompetencyReturns([competency]) + .setTempApplicant() + .setApplicationsReturn( + applications, + { + id: 'a256a733-ba09-446c-a3c7-59ca19e09e43', + local_id: 'testSpec1', + name: 'Test Spec 1', + assessment_hurdle_id: 'ec12d26a-9529-4b2e-a006-a57465af1f3e', + points_required: 20, + }, + [ + { + id: '3e0b841f-09f3-433b-9403-024ca1fe0fb3', + competency_id: '6fa17c61-c995-4776-9450-43e780ce4869', + specialty_id: 'a256a733-ba09-446c-a3c7-59ca19e09e43', + }, + { + id: '3cb28109-222f-4e8a-9129-1001914c17b6', + competency_id: '716c9aac-c2ff-4436-b270-ea3f453d6fdd', + specialty_id: 'a256a733-ba09-446c-a3c7-59ca19e09e43', + }, + { + id: '2ecf00f0-8ad5-4f2a-865b-24294d0c6b51', + competency_id: 'b7931ade-0d1a-4800-9dea-b6f3b1fc1ab6', + specialty_id: 'a256a733-ba09-446c-a3c7-59ca19e09e43', + }, + ], + ) + .setCompetencySelectorsReturn(compSelectors); + mocker + .setApplicationEvaluationReturnOf1(applications[0].id) + .setCompetencyEvalutionsReturnOf1(competency.id, compSelectors[0].id) + .setApplicationEvalComptencyReturn({ + id: 'c4f1e8ab-daa3-4e1a-8c86-88e7cfaeceaf', + application_evaluation_id: 'd542b471-6ad9-42f4-8fbd-334eab7e34e7', + competency_evaluation_id: '5ea8b22d-d61d-44bd-95af-cf53ca05b648', + }) + .setApplicationAssignmentReturn([ + { + applicant_id: 'ea2092bf-a290-4695-bd1e-cbbe442c621e', + evaluator_id: '044bb051-2de5-4bca-a3ab-d5653b772995', + id: 'ec3c98fd-3d8c-4802-97e2-18e85215dc79', + active: false, + assessment_hurdle_id: 'ae479190-8e18-458e-a68f-0d6a89a87b2b', + }, + ]); + const svc = new EvaluationService(); + await expect(svc.submitEvaluation(dbInstance, body, assessmentHurdleId, applicantId, evaluatorId)).rejects.toThrowError(HttpException); + mocker.expectMockToBeCalled(EvaluationMocks.MockName.applications); + mocker.expectMockToBeCalled(EvaluationMocks.MockName.competencySelectors); + mocker.expectMockToBeCalled(EvaluationMocks.MockName.applicationEvaluations, 0); + }); + it('missing review for failing competency throw 400', async () => { + const assessmentHurdleId = 'ec12d26a-9529-4b2e-a006-a57465af1f3e'; + const evaluatorId = 'fc81850e-1bbd-4e28-98ed-7d91dfe86b38'; + const applicantId = 'ea2092bf-a290-4695-bd1e-cbbe442c621e'; + const body: EvaluationSubmitDto = { + note: 'Experience is good.', + evaluatorId, + competencyEvals: [ + { + competencyId: '6fa17c61-c995-4776-9450-43e780ce4869', + selectorId: 'dcb4e5cf-0b8a-41a4-94dd-fda5c74e4755', + note: 'passed with flying colors', + }, + { + competencyId: '6fa17c61-c995-4776-9450-43e780ce4869', + selectorId: '376eb971-0bc3-4251-80ce-33b4a434f124', + note: '', + }, + ], + }; + const applications = [ + { + id: 'd84f9a55-80b1-445e-a52b-b98d396de520', + applicant_id: 'ea2092bf-a290-4695-bd1e-cbbe442c621e', + specialty_id: 'a256a733-ba09-446c-a3c7-59ca19e09e43', + }, + ]; + const compSelectors = [ + { + id: 'dcb4e5cf-0b8a-41a4-94dd-fda5c74e4755', + competency_id: '6fa17c61-c995-4776-9450-43e780ce4869', + default_text: 'Default Text', + display_name: 'Selector Default', + point_value: 20, + sort_order: 1, + }, + { + id: '376eb971-0bc3-4251-80ce-33b4a434f124', + competency_id: '716c9aac-c2ff-4436-b270-ea3f453d6fdd', + default_text: 'Meet L33t', + display_name: 'Meet', + point_value: 1, + sort_order: 2, + }, + ]; + + const competency = { + id: '6fa17c61-c995-4776-9450-43e780ce4869', + local_id: 'testComp1', + name: 'Test Comp 1', + definition: ' Ye[p', + assessment_hurdle_id: 'ec12d26a-9529-4b2e-a006-a57465af1f3e', + display_type: 1, + required_proficiency_definition: 'L33t', + screen_out: false, + }; + + const mocker = new EvaluationMocks() + .setCompetencyReturns([competency]) + .setTempApplicant() + .setApplicationsReturn( + applications, + { + id: 'a256a733-ba09-446c-a3c7-59ca19e09e43', + local_id: 'testSpec1', + name: 'Test Spec 1', + assessment_hurdle_id: 'ec12d26a-9529-4b2e-a006-a57465af1f3e', + points_required: 20, + }, + [ + { + id: '3e0b841f-09f3-433b-9403-024ca1fe0fb3', + competency_id: '6fa17c61-c995-4776-9450-43e780ce4869', + specialty_id: 'a256a733-ba09-446c-a3c7-59ca19e09e43', + }, + { + id: '3cb28109-222f-4e8a-9129-1001914c17b6', + competency_id: '716c9aac-c2ff-4436-b270-ea3f453d6fdd', + specialty_id: 'a256a733-ba09-446c-a3c7-59ca19e09e43', + }, + ], + ) + .setCompetencySelectorsReturn(compSelectors); + mocker + .setApplicationEvaluationReturnOf1(applications[0].id) + .setCompetencyEvalutionsReturnOf1(competency.id, compSelectors[0].id) + .setApplicationEvalComptencyReturn({ + id: 'c4f1e8ab-daa3-4e1a-8c86-88e7cfaeceaf', + application_evaluation_id: 'd542b471-6ad9-42f4-8fbd-334eab7e34e7', + competency_evaluation_id: '5ea8b22d-d61d-44bd-95af-cf53ca05b648', + }) + .setApplicationAssignmentReturn([ + { + applicant_id: 'ea2092bf-a290-4695-bd1e-cbbe442c621e', + evaluator_id: '044bb051-2de5-4bca-a3ab-d5653b772995', + id: 'ec3c98fd-3d8c-4802-97e2-18e85215dc79', + active: false, + assessment_hurdle_id: 'ae479190-8e18-458e-a68f-0d6a89a87b2b', + }, + ]); + const svc = new EvaluationService(); + await expect(svc.submitEvaluation(dbInstance, body, assessmentHurdleId, applicantId, evaluatorId)).rejects.toThrowError(HttpException); + mocker.expectMockToBeCalled(EvaluationMocks.MockName.applications); + mocker.expectMockToBeCalled(EvaluationMocks.MockName.competencySelectors); + mocker.expectMockToBeCalled(EvaluationMocks.MockName.applicationEvaluations, 0); + }); + it('missing review for a failing specialty throw 400', async () => { + const applicantId = 'ea2092bf-a290-4695-bd1e-cbbe442c621e'; + const assessmentHurdleId = 'ec12d26a-9529-4b2e-a006-a57465af1f3e'; + const evaluatorId = 'fc81850e-1bbd-4e28-98ed-7d91dfe86b38'; + const body: EvaluationSubmitDto = { + note: '', + evaluatorId, + competencyEvals: [ + { + competencyId: '6fa17c61-c995-4776-9450-43e780ce4869', + selectorId: 'dcb4e5cf-0b8a-41a4-94dd-fda5c74e4755', + note: 'passed with flying colors', + }, + { + competencyId: '6fa17c61-c995-4776-9450-43e780ce4869', + selectorId: '376eb971-0bc3-4251-80ce-33b4a434f124', + note: 'Did not show how to do 1 + 1!', + }, + ], + }; + const applications = [ + { + id: 'd84f9a55-80b1-445e-a52b-b98d396de520', + applicant_id: 'ea2092bf-a290-4695-bd1e-cbbe442c621e', + specialty_id: 'a256a733-ba09-446c-a3c7-59ca19e09e43', + }, + ]; + const compSelectors = [ + { + id: 'dcb4e5cf-0b8a-41a4-94dd-fda5c74e4755', + competency_id: '6fa17c61-c995-4776-9450-43e780ce4869', + default_text: 'Default Text', + display_name: 'Selector Default', + point_value: 10, + sort_order: 1, + }, + { + id: '376eb971-0bc3-4251-80ce-33b4a434f124', + competency_id: '716c9aac-c2ff-4436-b270-ea3f453d6fdd', + default_text: 'Does Not Meet L33t', + display_name: 'Does Not Meet', + point_value: 0, + sort_order: 2, + }, + ]; + + const competency = { + id: '6fa17c61-c995-4776-9450-43e780ce4869', + local_id: 'testComp1', + name: 'Test Comp 1', + definition: ' Ye[p', + assessment_hurdle_id: assessmentHurdleId, + display_type: 1, + required_proficiency_definition: 'L33t', + screen_out: false, + }; + + const mocker = new EvaluationMocks() + .setCompetencyReturns([competency]) + .setTempApplicant() + .setApplicationsReturn( + applications, + { + id: 'a256a733-ba09-446c-a3c7-59ca19e09e43', + local_id: 'testSpec1', + name: 'Test Spec 1', + assessment_hurdle_id: 'ec12d26a-9529-4b2e-a006-a57465af1f3e', + points_required: 20, + }, + [ + { + id: '3e0b841f-09f3-433b-9403-024ca1fe0fb3', + competency_id: '6fa17c61-c995-4776-9450-43e780ce4869', + specialty_id: 'a256a733-ba09-446c-a3c7-59ca19e09e43', + }, + { + id: '3cb28109-222f-4e8a-9129-1001914c17b6', + competency_id: '716c9aac-c2ff-4436-b270-ea3f453d6fdd', + specialty_id: 'a256a733-ba09-446c-a3c7-59ca19e09e43', + }, + ], + ) + .setCompetencySelectorsReturn(compSelectors); + mocker + .setApplicationEvaluationReturnOf1(applications[0].id) + .setCompetencyEvalutionsReturnOf1(competency.id, compSelectors[0].id) + .setApplicationEvalComptencyReturn({ + id: 'c4f1e8ab-daa3-4e1a-8c86-88e7cfaeceaf', + application_evaluation_id: 'd542b471-6ad9-42f4-8fbd-334eab7e34e7', + competency_evaluation_id: '5ea8b22d-d61d-44bd-95af-cf53ca05b648', + }) + .setApplicationAssignmentReturn([ + { + applicant_id: 'ea2092bf-a290-4695-bd1e-cbbe442c621e', + evaluator_id: '044bb051-2de5-4bca-a3ab-d5653b772995', + id: 'ec3c98fd-3d8c-4802-97e2-18e85215dc79', + active: false, + assessment_hurdle_id: 'ae479190-8e18-458e-a68f-0d6a89a87b2b', + }, + ]); + const svc = new EvaluationService(); + await expect(svc.submitEvaluation(dbInstance, body, assessmentHurdleId, applicantId, evaluatorId)).rejects.toThrowError(HttpException); + mocker.expectMockToBeCalled(EvaluationMocks.MockName.applications); + mocker.expectMockToBeCalled(EvaluationMocks.MockName.competencySelectors); + mocker.expectMockToBeCalled(EvaluationMocks.MockName.applicationEvaluations, 0); + }); + it('competency missing selector throw 500', async () => { + const applicantId = 'ea2092bf-a290-4695-bd1e-cbbe442c621e'; + const assessmentHurdleId = 'ec12d26a-9529-4b2e-a006-a57465af1f3e'; + const evaluatorId = 'fc81850e-1bbd-4e28-98ed-7d91dfe86b38'; + const body: EvaluationSubmitDto = { + note: 'Experience is good.', + evaluatorId, + competencyEvals: [ + { + competencyId: '6fa17c61-c995-4776-9450-43e780ce4869', + selectorId: '1cbbda39-939b-4dc2-9e6b-3d86abbf5a2e', + note: 'passed with flying colors', + }, + ], + }; + const applications = [ + { + id: 'd84f9a55-80b1-445e-a52b-b98d396de520', + applicant_id: 'ea2092bf-a290-4695-bd1e-cbbe442c621e', + specialty_id: 'a256a733-ba09-446c-a3c7-59ca19e09e43', + }, + ]; + const compSelectors = [ + { + id: 'dcb4e5cf-0b8a-41a4-94dd-fda5c74e4755', + competency_id: '6fa17c61-c995-4776-9450-43e780ce4869', + default_text: 'Default Text', + display_name: 'Selector Default', + point_value: 20, + sort_order: 1, + }, + ]; + const competency = { + id: '6fa17c61-c995-4776-9450-43e780ce4869', + local_id: 'testComp1', + name: 'Test Comp 1', + definition: ' Ye[p', + assessment_hurdle_id: assessmentHurdleId, + display_type: 1, + required_proficiency_definition: 'L33t', + screen_out: false, + }; + + const mocker = new EvaluationMocks() + .setCompetencyReturns([competency]) + .setTempApplicant() + .setApplicationsReturn( + applications, + { + id: 'a256a733-ba09-446c-a3c7-59ca19e09e43', + local_id: 'testSpec1', + name: 'Test Spec 1', + assessment_hurdle_id: 'ec12d26a-9529-4b2e-a006-a57465af1f3e', + points_required: 20, + }, + [ + { + id: '3e0b841f-09f3-433b-9403-024ca1fe0fb3', + competency_id: '6fa17c61-c995-4776-9450-43e780ce4869', + specialty_id: 'a256a733-ba09-446c-a3c7-59ca19e09e43', + }, + ], + ) + .setCompetencySelectorsReturn(compSelectors); + mocker + .setApplicationEvaluationReturnOf1(applications[0].id) + .setCompetencyEvalutionsReturnOf1(competency.id, compSelectors[0].id) + .setApplicationEvalComptencyReturn({ + id: 'c4f1e8ab-daa3-4e1a-8c86-88e7cfaeceaf', + application_evaluation_id: 'd542b471-6ad9-42f4-8fbd-334eab7e34e7', + competency_evaluation_id: '5ea8b22d-d61d-44bd-95af-cf53ca05b648', + }) + .setApplicationAssignmentReturn([ + { + applicant_id: 'ea2092bf-a290-4695-bd1e-cbbe442c621e', + evaluator_id: '044bb051-2de5-4bca-a3ab-d5653b772995', + id: 'ec3c98fd-3d8c-4802-97e2-18e85215dc79', + active: false, + assessment_hurdle_id: 'ae479190-8e18-458e-a68f-0d6a89a87b2b', + }, + ]); + + const svc = new EvaluationService(); + const [appEval, compEval] = await svc.submitEvaluation(dbInstance, body, assessmentHurdleId, applicantId, evaluatorId); + expect(appEval.length).toEqual(1); + expect(compEval.length).toEqual(1); + mocker.expectMockToBeCalled(EvaluationMocks.MockName.applications); + mocker.expectMockToBeCalled(EvaluationMocks.MockName.competencySelectors); + mocker.expectMockToBeCalled(EvaluationMocks.MockName.applicationEvaluations, 1); + }); + + it('not found applicant throws 404', async () => { + const mockFindByPk = jest.fn(); + const assessmentHurdleId = ''; + const evaluatorId = 'f3861704-923e-49ac-8a76-845f0745ad06'; + mockFindByPk.mockReturnValue(undefined); + Applicant.findByPk = mockFindByPk; + + const applicantId = 'bda70bd1-4e6f-4a60-97a9-aa03a1a0ddd9'; + const body: EvaluationSubmitDto = { + evaluatorId: 'f3861704-923e-49ac-8a76-845f0745ad06', + note: '', + competencyEvals: [], + }; + const svc = new EvaluationService(); + await expect(svc.submitEvaluation(dbInstance, body, assessmentHurdleId, applicantId, evaluatorId)).rejects.toThrowError(HttpException); + }); +}); diff --git a/api/src/tests/services.tests/evaluationmocks.ts b/api/src/tests/services.tests/evaluationmocks.ts new file mode 100644 index 0000000..f67bb80 --- /dev/null +++ b/api/src/tests/services.tests/evaluationmocks.ts @@ -0,0 +1,210 @@ +import { Applicant, ApplicantAttributes } from '../../models/applicant'; +import { Application, ApplicationAttributes } from '../../models/application'; +import { ApplicationAssignments, ApplicationAssignmentsAttributes } from '../../models/application_assignments'; +import { ApplicationEvaluation, ApplicationEvaluationAttributes } from '../../models/application_evaluation'; +import { ApplicationEvaluationCompetency, ApplicationEvaluationCompetencyAttributes } from '../../models/application_evaluation_competency'; +import { Competency, CompetencyAttributes } from '../../models/competency'; +import { CompetencyEvaluation, CompetencyEvaluationAttributes } from '../../models/competency_evaluation'; +import { CompetencySelectors, CompetencySelectorsAttributes } from '../../models/competency_selectors'; +import { SpecialtyAttributes } from '../../models/specialty'; +import { SpecialtyCompetenciesAttributes } from '../../models/specialty_competencies'; + +//#region jest mock +jest.mock('../../models/application'); +jest.mock('../../models/application_assignments'); +jest.mock('../../models/application_evaluation'); +jest.mock('../../models/application_evaluation_competency'); +jest.mock('../../models/applicant_evaluation_feedback'); +jest.mock('../../models/competency'); +jest.mock('../../models/competency_evaluation'); +jest.mock('../../models/competency_selectors'); +jest.mock('../../models/specialty'); +jest.mock('../../models/specialty_competencies'); +jest.mock('../../models/applicant'); +//#endregion + +export class EvaluationMocks { + mocks: Record = { + [EvaluationMocks.MockName.applications]: jest.fn(), + [EvaluationMocks.MockName.competencySelectors]: jest.fn(), + [EvaluationMocks.MockName.competencies]: jest.fn(), + [EvaluationMocks.MockName.competency]: jest.fn(), + [EvaluationMocks.MockName.applicationEvaluations]: jest.fn(), + [EvaluationMocks.MockName.competencyEvaluations]: jest.fn(), + [EvaluationMocks.MockName.applicationEvaluationComps]: jest.fn(), + [EvaluationMocks.MockName.applicationsAssignments]: jest.fn(), + [EvaluationMocks.MockName.applicantOne]: jest.fn(), + }; + + constructor() { + this.initMethods(); + } + initMethods() { + Application.findAll = this.mocks[EvaluationMocks.MockName.applications]; + CompetencySelectors.findAll = this.mocks[EvaluationMocks.MockName.competencySelectors]; + ApplicationEvaluation.bulkCreate = this.mocks[EvaluationMocks.MockName.applicationEvaluations]; + CompetencyEvaluation.bulkCreate = this.mocks[EvaluationMocks.MockName.competencyEvaluations]; + ApplicationEvaluationCompetency.bulkCreate = this.mocks[EvaluationMocks.MockName.applicationEvaluationComps]; + ApplicationAssignments.update = this.mocks[EvaluationMocks.MockName.applicationsAssignments]; + Applicant.findByPk = this.mocks[EvaluationMocks.MockName.applicantOne]; + Competency.findAll = this.mocks[EvaluationMocks.MockName.competencies]; + Competency.findByPk = this.mocks[EvaluationMocks.MockName.competency]; + } + + expectMockToBeCalled(mockName: EvaluationMocks.MockName, num = 1) { + const mck = this.mocks[mockName]; + if (num === 0) expect(mck).not.toBeCalled(); + else expect(mck).toBeCalledTimes(num); + } + + setCompetencyReturn(comp: CompetencyAttributes): this { + this.mocks[EvaluationMocks.MockName.competency].mockReturnValue(comp); + return this; + } + + setCompetencyReturns(comps: CompetencyAttributes[]): this { + this.mocks[EvaluationMocks.MockName.competencies].mockReturnValue(comps); + return this; + } + + setApplicationsReturn(apps: ApplicationAttributes[], specialty: SpecialtyAttributes, specComps: SpecialtyCompetenciesAttributes[]): this { + const specCompsMapped = specComps.map(sc => { + return { + id: sc.id, + specialty_id: sc.specialty_id, + competency_id: sc.competency_id, + Competency: this.mocks[EvaluationMocks.MockName.competency], + getCompetency: this.mocks[EvaluationMocks.MockName.competency], + }; + }); + + const specialtyMap = { + id: specialty.id, + local_id: specialty.local_id, + assessment_hurdle_id: specialty.assessment_hurdle_id, + name: specialty.name, + points_required: specialty.points_required, + created_at: specialty.created_at, + updated_at: specialty.updated_at, + SpecialtyCompetencies: specCompsMapped, + getSpecialtyCompetencies: () => { + return specCompsMapped; + }, + }; + + const transformed = apps.map(a => { + return { + id: a.id, + applicant_id: a.applicant_id, + specialty_id: a.specialty_id, + created_at: a.created_at, + updated_at: a.updated_at, + Specialty: specialtyMap, + getSpecialty: () => { + return specialtyMap; + }, + }; + }); + this.mocks[EvaluationMocks.MockName.applications].mockReturnValue(transformed); + return this; + } + + setCompetencySelectorsReturn(selectors: CompetencySelectorsAttributes[]): this { + this.mocks[EvaluationMocks.MockName.competencySelectors].mockReturnValue(selectors); + return this; + } + + setApplicationEvalutionsReturn(appEvals: ApplicationEvaluationAttributes[]): this { + this.mocks[EvaluationMocks.MockName.applicationEvaluations].mockReturnValue(appEvals); + return this; + } + + setApplicationEvaluationPassthroughReturn(): this { + this.mocks[EvaluationMocks.MockName.applicationEvaluations].mockImplementation((props: any) => props); + return this; + } + + setApplicationEvaluationReturnOf1(applicationId: string): this { + this.mocks[EvaluationMocks.MockName.applicationEvaluations].mockReturnValue([ + { + id: '675710c7-6969-4496-8070-53f4a86f466e', + evaluation_note: 'string', + evaluator: 'string', + approved: false, + approver_id: undefined, + feedback_timestamp: new Date(), + application_id: applicationId, + created_at: new Date(), + updated_at: new Date(), + }, + ]); + return this; + } + setCompetencyEvalutionsReturn(compEvals: CompetencyEvaluationAttributes[]): this { + this.mocks[EvaluationMocks.MockName.competencyEvaluations].mockReturnValue(compEvals); + return this; + } + setCompetencyEvalutionsReturnOf1(compId: string, selectorId: string): this { + this.mocks[EvaluationMocks.MockName.competencyEvaluations].mockReturnValue([ + { + id: '2bad8d90-b00d-4fa1-bfa5-09e243362669', + evaluation_note: 'string', + evaluator: '62589232-bfb0-4063-9826-26070999eb2a', + competency_id: compId, + competency_selector_id: selectorId, + approved: false, + approver_id: undefined, + created_at: new Date(), + updated_at: new Date(), + }, + ]); + return this; + } + + setCompetencyEvaluationPassThroughReturn(): this { + this.mocks[EvaluationMocks.MockName.competencyEvaluations].mockImplementation((props: any) => props); + return this; + } + setApplicationEvalComptencyReturn(mappings: ApplicationEvaluationCompetencyAttributes): this { + this.mocks[EvaluationMocks.MockName.applicationEvaluationComps].mockReturnValue(mappings); + return this; + } + + setApplicationAssignmentReturn(assignments: ApplicationAssignmentsAttributes[]): this { + this.mocks[EvaluationMocks.MockName.applicationsAssignments].mockReturnValue([assignments.length, assignments]); + return this; + } + + setTempApplicant(): this { + this.mocks[EvaluationMocks.MockName.applicantOne].mockReturnValue({ + id: 'ea24be2f-f038-4c18-ab2f-f61771945d9c', + name: 'string', + flag_type: 0, + flag_message: 'string', + assessment_hurdle_id: '004a7bb6-509a-4c95-9652-485053c22398', + additional_note: 'string', + created_at: new Date(), + updated_at: new Date(), + }); + + return this; + } + + setApplicant(applicant: ApplicantAttributes): this { + this.mocks[EvaluationMocks.MockName.applicantOne].mockReturnValue(applicant); + return this; + } +} +export namespace EvaluationMocks { + export enum MockName { + applications, + competencySelectors, + competencies, + competency, + applicationEvaluations, + competencyEvaluations, + applicationEvaluationComps, + applicationsAssignments, + applicantOne, + } +} diff --git a/api/src/tests/services.tests/specialty.service.spec.ts b/api/src/tests/services.tests/specialty.service.spec.ts new file mode 100644 index 0000000..cdf0c2a --- /dev/null +++ b/api/src/tests/services.tests/specialty.service.spec.ts @@ -0,0 +1,184 @@ +import CreateSpecialtyDto from '../../dto/createspecialty.dto'; +import HttpException from '../../exceptions/HttpException'; +import { Competency } from '../../models/competency'; +import { Specialty } from '../../models/specialty'; +import { SpecialtyCompetencies } from '../../models/specialty_competencies'; +import SpecialtyService from '../../services/specialty.service'; + +jest.mock('../../models/specialty'); +jest.mock('../../models/competency'); +jest.mock('../../models/specialty_competencies'); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('getBy tests', () => { + it('getById return values', async () => { + const mockFindByPk = jest.fn(); + mockFindByPk.mockReturnValue({ + id: 'd5acadd1-015a-46a9-b147-71ad84af8d87', + local_id: 'local', + name: 'Name', + assessment_hurdle_id: 'a321bdde-2494-4778-8b00-1429482ce0db', + points_required: 1404, + created_at: new Date(), + updated_at: new Date(), + }); + + Specialty.findByPk = mockFindByPk; + + const svc = new SpecialtyService(); + const rst = await svc.getById('d5acadd1-015a-46a9-b147-71ad84af8d87'); + expect(mockFindByPk).toBeCalledTimes(1); + expect(rst.id).toEqual('d5acadd1-015a-46a9-b147-71ad84af8d87'); + }); + + it('getById returns 400 for empty input', async () => { + const svc = new SpecialtyService(); + await expect(svc.getById('')).rejects.toThrowError(HttpException); + }); + it('getById returns 404 for not found value', async () => { + const mockFindByPk = jest.fn(); + mockFindByPk.mockReturnValue(undefined); + Specialty.findByPk = mockFindByPk; + const svc = new SpecialtyService(); + await expect(svc.getById('d5acadd1-015a-46a9-b147-71ad84af8d87')).rejects.toThrowError(HttpException); + }); + it('getAllMappingsById returns value', async () => { + const mockFindAll = jest.fn(); + mockFindAll.mockReturnValue([ + { + id: 'c271341e-1c2e-4395-a43e-7de7990a0e53', + specialty_id: '12157d0e-1e8f-4023-b4d6-87152620f747', + competency_id: 'b9b8decb-9326-401c-9696-ab2e7205022f', + created_at: new Date(), + updated_at: new Date(), + }, + ]); + SpecialtyCompetencies.findAll = mockFindAll; + + const mockCount = jest.fn(); + mockCount.mockReturnValue(1); + + Specialty.count = mockCount; + + const svc = new SpecialtyService(); + const rst = await svc.getAllMappingsById('12157d0e-1e8f-4023-b4d6-87152620f747', 'a321bdde-2494-4778-8b00-1429482ce0db'); + expect(mockFindAll).toBeCalledTimes(1); + expect(mockCount).toBeCalledTimes(1); + expect(rst.length).toEqual(1); + }); + it('getAllMappingsById returns 400 for empty input', async () => { + const svc = new SpecialtyService(); + await expect(svc.getAllMappingsById('', '699b6b20-9ad7-4f43-b383-fe355e4459a5')).rejects.toThrowError(HttpException); + }); + it('getAllMappingsById returns 400 for bad specialtyId', async () => { + const mockCount = jest.fn(); + mockCount.mockReturnValue(0); + + Specialty.count = mockCount; + const svc = new SpecialtyService(); + await expect(svc.getAllMappingsById('0a14c115-092f-4dee-81f8-8c6c19c508b7', '699b6b20-9ad7-4f43-b383-fe355e4459a5')).rejects.toThrowError( + HttpException, + ); + }); +}); + +describe('upsert Tests', () => { + it('upsert creates items and mapping', async () => { + const mockedSpecialtyId = '17a7ff65-276b-4f3f-b83c-755a7d7c4c7f'; + const mockedHurdleId = '162ad702-cb0b-4cd5-93c6-a75bb61d9df0'; + const mockedCompetencyId = '9ca97f0b-ea04-4c57-a3eb-dca828773b8c'; + const mockUpsert = jest.fn(); + mockUpsert.mockReturnValue([ + { + id: mockedSpecialtyId, + local_id: 'local', + name: 'Name', + assessment_hurdle_id: mockedHurdleId, + points_required: 239394, + }, + true, + ]); + Specialty.upsert = mockUpsert; + + const mockFindOrCreate = jest.fn(); + + mockFindOrCreate.mockReturnValue([ + { + id: '7550b8e2-73a6-462e-9896-7bed49e35674', + specialty_id: mockedSpecialtyId, + competency_id: mockedCompetencyId, + created_at: new Date(), + updated_at: new Date(), + }, + true, + ]); + SpecialtyCompetencies.findOrCreate = mockFindOrCreate; + const findAllCompetenciesMock = jest.fn(); + findAllCompetenciesMock.mockReturnValue([{ local_id: 'comp1', id: mockedCompetencyId }]); + Competency.findAll = findAllCompetenciesMock; + + const svc = new SpecialtyService(); + const body: CreateSpecialtyDto = { + localId: 'localId', + existingId: undefined, + assessmentHurdleId: mockedHurdleId, + name: 'Name', + pointsRequired: 15, + competencyLocalIds: ['comp1'], + }; + jest.spyOn(SpecialtyCompetencies, 'findOrCreate'); + + const rst = await svc.upsert(body); + expect(mockUpsert).toBeCalledTimes(1); + expect(mockFindOrCreate).toBeCalledTimes(1); + expect(mockFindOrCreate).toHaveBeenCalledWith({ + where: { specialty_id: mockedSpecialtyId, competency_id: mockedCompetencyId }, + defaults: { + competency_id: mockedCompetencyId, + specialty_id: mockedSpecialtyId, + }, + }); + expect(rst.id).toEqual(mockedSpecialtyId); + }); + + it('upsert does not create mapping with empty competency ids', async () => { + const mockedSpecialtyId = '17a7ff65-276b-4f3f-b83c-755a7d7c4c7f'; + const mockedHurdleId = '162ad702-cb0b-4cd5-93c6-a75bb61d9df0'; + const mockedCompetencyId = '9ca97f0b-ea04-4c57-a3eb-dca828773b8c'; + const mockUpsert = jest.fn(); + mockUpsert.mockReturnValue([ + { + id: mockedSpecialtyId, + local_id: 'local', + name: 'Name', + assessment_hurdle_id: mockedHurdleId, + points_required: 239394, + }, + true, + ]); + Specialty.upsert = mockUpsert; + + const mockFindOrCreate = jest.fn(); + + SpecialtyCompetencies.findOrCreate = mockFindOrCreate; + const svc = new SpecialtyService(); + const body: CreateSpecialtyDto = { + localId: 'localId', + existingId: undefined, + assessmentHurdleId: mockedHurdleId, + name: 'Name', + pointsRequired: 15, + competencyLocalIds: [], + }; + const findAllCompetenciesMock = jest.fn(); + findAllCompetenciesMock.mockReturnValue([{ local_id: 'comp1', id: mockedCompetencyId }]); + Competency.findAll = findAllCompetenciesMock; + const rst = await svc.upsert(body); + expect(mockUpsert).toBeCalledTimes(1); + expect(mockFindOrCreate).toBeCalledTimes(0); + expect(rst.id).toEqual(mockedSpecialtyId); + }); +}); diff --git a/api/src/tests/services.tests/users.service.spec.ts b/api/src/tests/services.tests/users.service.spec.ts new file mode 100644 index 0000000..716ee0a --- /dev/null +++ b/api/src/tests/services.tests/users.service.spec.ts @@ -0,0 +1,141 @@ +// import request from 'supertest'; +// import App from '../../app'; +// import CreateUserDto from '../../dto/createuser.dto'; +import CreateHurdleUserDto, { CreateHurdleUserPairDto } from '../../dto/createhurdleuser.dto'; +import CreateUserDto from '../../dto/createuser.dto'; +import HttpException from '../../exceptions/HttpException'; +import { AppUser } from '../../models/app_user'; +import { AssessmentHurdleUser } from '../../models/assessment_hurdle_user'; +//import UsersRoute from '../../routes/users.routes'; +import UserService from '../../services/users.service'; + +jest.mock('../../models/app_user'); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('getBy Tests', () => { + it('getById returns value', async () => { + const mockFindByPk = jest.fn(); + mockFindByPk.mockReturnValue({ + id: '0f0fb145-5e26-4732-b72d-73560645a544', + email: 'testuser@omb.eop.gov', + name: 'Steve Tester', + meta_user_type: 1, + created_at: new Date(), + updated_at: new Date(), + }); + + AppUser.findByPk = mockFindByPk; + + const svc = new UserService(); + const user = await svc.getUserById('0f0fb145-5e26-4732-b72d-73560645a544'); + expect(mockFindByPk).toBeCalledTimes(1); + expect(user.email).toEqual('testuser@omb.eop.gov'); + }); + + it('getById throws 400 error for empty value', async () => { + const svc = new UserService(); + await expect(svc.getUserById('')).rejects.toThrowError(HttpException); + }); + + it('getByID throws 404 error for not found user', async () => { + const mockFindByPk = jest.fn(); + mockFindByPk.mockReturnValue(undefined); + + AppUser.findByPk = mockFindByPk; + const svc = new UserService(); + await expect(svc.getUserById('0122a8d8-a183-400d-93fe-b59c03eb4075')).rejects.toThrowError(HttpException); + expect(mockFindByPk).toBeCalledTimes(1); + }); + + it('getByEmail returns user', async () => { + const mockFindByPk = jest.fn(); + mockFindByPk.mockReturnValue({ + id: '0f0fb145-5e26-4732-b72d-73560645a544', + email: 'testuser@omb.eop.gov', + name: 'Steve Tester', + meta_user_type: 1, + created_at: new Date(), + updated_at: new Date(), + }); + + AppUser.findOne = mockFindByPk; + + const svc = new UserService(); + const rst = await svc.getUserByEmail('testuser@omb.eop.gov'); + expect(rst.email).toEqual('testuser@omb.eop.gov'); + }); + + it('getByEmail returns 400 for emptyEmail', async () => { + const svc = new UserService(); + await expect(svc.getUserByEmail('')).rejects.toThrowError(HttpException); + }); + + it('getByEmail returns 404 for not found email', async () => { + const mockFindByPk = jest.fn(); + mockFindByPk.mockReturnValue(undefined); + + AppUser.findByPk = mockFindByPk; + const svc = new UserService(); + await expect(svc.getUserByEmail('testuser@omb.eop.gov')).rejects.toThrowError(HttpException); + }); +}); + +describe('createUserAndAddToHurdle Tests', () => { + it('returns 400 for missing hurdle Id', async () => { + const svc = new UserService(); + const body = new CreateHurdleUserDto(); + const hurdleId = ''; + await expect(svc.createUserAndAddToHurdle(body, hurdleId)).rejects.toThrowError(HttpException); + }); + + it('findOrCreate called correct number of times for single input', async () => { + const appUserFindOrCreate = jest.fn(); + const hurdleUserFindOrCreate = jest.fn(); + + AppUser.findOrCreate = appUserFindOrCreate; + AssessmentHurdleUser.findOrCreate = hurdleUserFindOrCreate; + + appUserFindOrCreate.mockReturnValue([ + { + id: '0f0fb145-5e26-4732-b72d-73560645a544', + email: 'test.hr@omb.eop.gov', + name: 'Test HR', + meta_user_type: 1, + created_at: new Date(), + updated_at: new Date(), + }, + true, + ]); + + hurdleUserFindOrCreate.mockReturnValue([ + { + app_user_id: '0f0fb145-5e26-4732-b72d-73560645a544', + assessment_hurdle_id: 'da269580-0b7d-47d3-863a-5acf23fa371d', + role: 1, + }, + true, + ]); + + const svc = new UserService(); + const body = new CreateHurdleUserDto(); + const assessmentHurdleId = 'da269580-0b7d-47d3-863a-5acf23fa371d'; + const pair: CreateHurdleUserPairDto = { + role: 1, + users: [new CreateUserDto('test.hr@omb.eop.gov', 'Test HR')], + }; + + body.userSetup = [pair]; + + const rst = await svc.createUserAndAddToHurdle(body, assessmentHurdleId); + expect(rst).toHaveLength(1); + expect(appUserFindOrCreate).toBeCalledTimes(1); + expect(hurdleUserFindOrCreate).toBeCalledTimes(1); + + const single = rst[0]; + expect(single.app_user_id).toEqual('0f0fb145-5e26-4732-b72d-73560645a544'); + expect(single.assessment_hurdle_id).toEqual('da269580-0b7d-47d3-863a-5acf23fa371d'); + }); +}); diff --git a/api/src/utils/isEmpty.ts b/api/src/utils/isEmpty.ts new file mode 100644 index 0000000..f682817 --- /dev/null +++ b/api/src/utils/isEmpty.ts @@ -0,0 +1,13 @@ +export const isEmpty = (value: any): boolean => { + if (value === null) { + return true; + } else if (typeof value !== 'number' && value === '') { + return true; + } else if (value === 'undefined' || value === undefined) { + return true; + } else if (value !== null && typeof value === 'object' && !Object.keys(value).length) { + return true; + } else { + return false; + } +}; diff --git a/api/src/utils/logger.ts b/api/src/utils/logger.ts new file mode 100644 index 0000000..8f42e64 --- /dev/null +++ b/api/src/utils/logger.ts @@ -0,0 +1,33 @@ +import winston from 'winston'; + +// winston format +const { combine, timestamp, printf } = winston.format; + +// Define log format +const logFormat = printf(({ timestamp, level, message }) => `${timestamp} ${level}: ${message}`); + +/* + * Log Level + * error: 0, warn: 1, info: 2, http: 3, verbose: 4, debug: 5, silly: 6 + */ +const logger = winston.createLogger({ + format: combine( + timestamp({ + format: 'YYYY-MM-DD HH:mm:ss', + }), + logFormat, + ), + transports: [ + new winston.transports.Console({ + format: winston.format.combine(winston.format.splat(), winston.format.colorize(), winston.format.simple()), + level: process.env.LOG_LEVEL === 'prod' ? 'info' : 'debug', + }), + ], +}); +const stream = { + write: (message: string) => { + logger.info(message.substring(0, message.lastIndexOf('\n'))); + }, +}; + +export { logger, stream }; diff --git a/api/tsconfig.json b/api/tsconfig.json new file mode 100644 index 0000000..70c6a1a --- /dev/null +++ b/api/tsconfig.json @@ -0,0 +1,71 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, + "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, + "lib": ["es6", "dom", "es2019"] /* Specify library files to be included in the compilation. */, + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + // "declaration": true, /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + "sourceMap": true /* Generates corresponding '.map' file. */, + // "outFile": "./", /* Concatenate and emit output to single file. */ + "outDir": "./build" /* Redirect output structure to the directory. */, + "rootDir": "src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, + // "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + "strict": true /* Enable all strict type-checking options. */, + "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */, + "strictNullChecks": true /* Enable strict null checks. */, + "strictFunctionTypes": true /* Enable strict checking of function types. */, + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + "alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */, + + /* Additional Checks */ + "noUnusedLocals": false /* Report errors on unused locals. */, + "noUnusedParameters": false /* Report errors on unused parameters. */, + "noImplicitReturns": true /* Report error when not all code paths in function return a value. */, + "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */, + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + + /* Module Resolution Options */ + "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + "typeRoots": ["src/@types", "node_modules/@types"] /* List of folders to include type definitions from. */, + // "types": ["request.d.ts"] /* Type declaration files to be included in compilation. */, + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */, + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + + /* Advanced Options */ + "skipLibCheck": true /* Skip type checking of declaration files. */, + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + }, + "exclude": ["node_modules", "jest.config.js", "src/tests"] +} diff --git a/db/README.md b/db/README.md new file mode 100644 index 0000000..90c6814 --- /dev/null +++ b/db/README.md @@ -0,0 +1,172 @@ +# Schema +[Schema](../backend/db) + +# Connecting to the database -- Development & Staging + +Staging is done on primarily in gsa-open-ops cloud.gov space. It is a postgresql on AWS RDS. + +Using the command line, you can "bind" an app with a service. The hosted backend should be "bound" with the hosted db. + +### Step 1: Log in and point your target to the sandbox. + +Refer to the [docs](https://cloud.gov/docs/apps/managed-services/) + +### Step 2: Create a service key + +You will need a service key for the backend to connect to the DB + +You can check if the API call to create the db has been completed by looking at the `last operation`, which should say "create succeeded": + +> cf services + +Just because the `create db` command was run doesn't mean the service has finished provisioning. Try to create a key to verify: + +> cf create-service-key smeqa-db smeqa-db-service-key + +Note: You may need to wait a few minutes before this command works. + +### Step 3: Use credentials to connect locally running backend to sandbox + +1. Copy/paste the contents of `backend/template-env.js` into a new file, `backend/.env`. + +2. Use the service key to get your credentials by running: + + > cf service-key smeqa-db smeqa-db-service-key + +3. Copy the credentials to your newly created `.env` file + +### Step 4: Set-up ssh-port forwarding + +Your local backend will try to connect to the cloud.gov DB directly over port 5432. Unfortunately, direct connections to the database do not work unless you're in the same space as the DB. + +To work around this, we need to setup a "host app" that uses ssh port-forwarding to communicate with the DB. Although we can create a dedicated host app for this purpose, we can use the existing `smeqa` cloud.gov app as the host. + +More details are here: +https://docs.cloudfoundry.org/devguide/deploy-apps/ssh-services.html + +In a separate terminal window, run the following: + + + + +``` +// login +> cf login -a api.fr.cloud.gov --sso + +// get the guid of the smeqa app +// this host app that tunnels our request to the db +> myapp_guid=$(cf app --guid smeqa-staging) + +// define the db host and port +// this is where the host app should forward requests to. +// Call this destination `tunnel` +myapp_guid=$(cf app --guid smeqa-staging); tunnel=$(cf curl /v2/apps/$myapp_guid/env | jq -r '[.system_env_json.VCAP_SERVICES."aws-rds"[0].credentials | .host, .port] | join(":")'); cf ssh -N -L 5432:$tunnel smeqa-staging + +// actually do the port forwarding +// this tells the host app to auto-forward incoming requests on port 5432 to the the db at the `tunnel` path defined above. The db is also happening to listen to port 5432 +cf ssh -N -L 5432:$tunnel smeqa-staging +``` + +TODO: add this to a `prestart` script + +# Managing the Sandbox DB + +To wipe all data in the sandbox DB and repopulate it with staging data, run the following script: + +> yarn reset-db + +This deletes all tables, recreates them, and populates it with fake data. + +TODO: create fake data to populate database + +# Setting up the Sandbox Database + +You should only have to do this once + +Most of the setup steps below references the documentation: +https://cloud.gov/docs/services/relational-database/ + +### Step 1: Log in and point your target to the sandbox. Refer to the [docs](https://cloud.gov/docs/apps/managed-services/) + +### Step 2: Create the DB service in the sandbox space + +> cf create-service aws-rds shared-psql smeqa-sandbox-db -c '{"storage": 1}' + +We're only specifying 1GB of space since we don't need a lot for the sandbox version. This can change in production, but should be small nonetheless + +For production, there is a different aws-rds plan to specify. See the [list of plans](https://cloud.gov/docs/services/relational-database/) for options and configuration parameters + +### Step 3: Connect the hosted backend to the sandbox db + +#### Step 3a. (optional) Manually bind the backend with the db + +The backend app would need credentials to read/write to the DB service. Passing credentials "binds" and app with a service. There are two ways of binding the app: + +> cf bind-service smeqa-backend smeqa-db + +Note: At this point, your target should be the sandbox space, though this same command would work in the production space + +#### Step 3b. Update the manifest.yml for automatic binding during deploys + +Instead of 3a, you an add the db service in the backend's `manifest.yml` to automatically bind the service whenever you push the backend. + +``` +... +services: + - smeqa-db +``` + +Binding a service creates a `DATABASE_URL` environment variable for the backend app, which contains the the credentials to connect to the db. + +#### Step 3c. (optional) Verify the backend and db have been connected via ssh + +There are a few ways, but the easiest is to use this tool: +https://github.com/18F/cf-service-connect#readme + +> cf connect-to-service smeqa smeqa-db + +### Step 4: Setup your locally running backend to connect to the sandbox + +You will need a service key for the backend to connect to the DB + +You can check if the API call to create the db has been completed by looking at the `last operation`, which should say "create succeeded": + +> cf services + +Just because the `create db` command was run doesn't mean the service has finished provisioning. Try to create a key to verify: + +> cf create-service-key smeqa-db smeqa-db-service-key + +Note: You may need to wait a few minutes before this command works. + +After the key is created, you need to get your credentials from it: + +> cf service-key smeqa-db smeqa-db-service-key + +### Step 5: Use credentials to connect locally running backend to sandbox + +Copy the credentials created above to your environment variables file at `backend/.env` + +### Step 6: Set-up ssh-port forwarding + +Your local backend will try to connect to the cloud.gov db directly over port 5432. Unfortunately, direct connections to the database do not work unless you're in the same space as the DB. + +To work around this, we need to setup a "host app" that uses ssh port-forwarding to communicate with the db. Although we can create a dedicated host app for this purpose, we can use the existing `smeqa` cloud.gov app as the host. + +More details are here: +https://docs.cloudfoundry.org/devguide/deploy-apps/ssh-services.html + +``` +// get the guid of the smeqa app +// this host app that tunnels our request to the db +> myapp_guid=$(cf app --guid smeqa) + +// define the db host and port +// this is where the host app should forward requests to. +// Call this destination `tunnel` +tunnel=$(cf curl /v2/apps/$myapp_guid/env | jq -r '[.system_env_json.VCAP_SERVICES."aws-rds"[0].credentials | .host, .port] | join(":")') + +// actually do the port forwarding +// this tells the host app to auto-forward incoming requests on port 5432 to the the db at the `tunnel` path defined above. The db is also happening to listen to port 5432 +cf ssh -N -L 5432:$tunnel smeqa +``` diff --git a/db/create_local_admin.sql b/db/create_local_admin.sql new file mode 100644 index 0000000..05e61bb --- /dev/null +++ b/db/create_local_admin.sql @@ -0,0 +1,11 @@ +INSERT INTO app_user ( + id + ,email + ,name + ,meta_user_type + ) VALUES ( + 'fafbf128-a3b0-4930-94ca-b8242f58721e' + ,'admin@usds.gov' + ,'admin' + ,1 + ); \ No newline at end of file diff --git a/db/delete_tables.sql b/db/delete_tables.sql new file mode 100644 index 0000000..18ce9d3 --- /dev/null +++ b/db/delete_tables.sql @@ -0,0 +1,17 @@ +-- delete all tables -- +-- TODO: delete hiring_action_hr_user and hiring_action_sme_user once we migrate off them +DROP TABLE IF EXISTS + application + , app_user + , hiring_action + , hiring_action_app_user + , competency + , competency_review + , specialty + , specialty_result + , pending_application + , specialty_result_competency_review + , application_specialty + , audit_log + , analytics + CASCADE;-- delete all tables -- \ No newline at end of file diff --git a/db/migrations/01_drop_table.sql b/db/migrations/01_drop_table.sql new file mode 100644 index 0000000..bdc1cbd --- /dev/null +++ b/db/migrations/01_drop_table.sql @@ -0,0 +1,2 @@ +DROP SCHEMA public CASCADE; +CREATE SCHEMA public; \ No newline at end of file diff --git a/db/migrations/02_create_extensions.sql b/db/migrations/02_create_extensions.sql new file mode 100644 index 0000000..cdfc1e1 --- /dev/null +++ b/db/migrations/02_create_extensions.sql @@ -0,0 +1,10 @@ +CREATE EXTENSION pgcrypto; +CREATE EXTENSION "uuid-ossp"; + +CREATE OR REPLACE FUNCTION update_with_changes() +RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = NOW(); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; \ No newline at end of file diff --git a/db/migrations/03_add_update_trigger.sql b/db/migrations/03_add_update_trigger.sql new file mode 100644 index 0000000..8008d6e --- /dev/null +++ b/db/migrations/03_add_update_trigger.sql @@ -0,0 +1,7 @@ +CREATE OR REPLACE FUNCTION update_with_changes() +RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = NOW(); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; \ No newline at end of file diff --git a/db/migrations/04_add_session_table.sql b/db/migrations/04_add_session_table.sql new file mode 100644 index 0000000..b756f03 --- /dev/null +++ b/db/migrations/04_add_session_table.sql @@ -0,0 +1,10 @@ +CREATE TABLE "session" ( + "sid" varchar NOT NULL COLLATE "default", + "sess" json NOT NULL, + "expire" timestamp(6) NOT NULL +) +WITH (OIDS=FALSE); + +ALTER TABLE "session" ADD CONSTRAINT "session_pkey" PRIMARY KEY ("sid") NOT DEFERRABLE INITIALLY IMMEDIATE; + +CREATE INDEX "IDX_session_expire" ON "session" ("expire"); \ No newline at end of file diff --git a/db/migrations/10_create_sme_qa.sql b/db/migrations/10_create_sme_qa.sql new file mode 100644 index 0000000..690090c --- /dev/null +++ b/db/migrations/10_create_sme_qa.sql @@ -0,0 +1,264 @@ +CREATE TABLE IF NOT EXISTS SMEQA_DB_VERSION( + id UUID PRIMARY KEY DEFAULT gen_random_uuid() + , migration_number INT +); +CREATE TABLE IF NOT EXISTS app_user ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid() + , email VARCHAR NOT NULL UNIQUE + , name VARCHAR + , created_at TIMESTAMP DEFAULT NOW() + , updated_at TIMESTAMP DEFAULT NOW() + , constraint unique_email UNIQUE(email) +); + + +CREATE TABLE IF NOT EXISTS assessment_hurdle ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid() + , department_name VARCHAR not null -- optional: ie: "Department of Interior", "Government Wide" + , component_name VARCHAR -- optional: ie: "National Park Service" + , position_name VARCHAR NOT NULL -- ie: "IT Specialist" + , assessment_name VARCHAR NOT NULL -- ie: "Structured Interview #1" + , position_details VARCHAR -- ie: "GS 12/GS 12-15" + , locations VARCHAR -- ie: "Washington, DC; Chicago, IL, Remote" + , start_datetime TIMESTAMP NOT NULL + , end_datetime TIMESTAMP NOT NULL + , hurdle_display_type INT NOT NULL -- written assessment, resume review, etc + , evaluations_required INT NOT NULL default 2 -- number of evaluations required for concensus per competency (e.g. 2) + , require_review_for_all_passing BOOLEAN default FALSE + , hr_name VARCHAR -- could be distinct for te HR users of the tool + , hr_email VARCHAR -- could be distinct from the HR users of the tool, and might not be in same agency + , created_at TIMESTAMP DEFAULT NOW() + , updated_at TIMESTAMP DEFAULT NOW() +); + +CREATE TABLE IF NOT EXISTS assessment_hurdle_meta ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid() + , staffing_vacancy_id VARCHAR NOT NULL UNIQUE -- unique across all hurdles of a hiring action + , staffing_assessment_id VARCHAR NOT NULL -- unique per hurdle (ie: all RR are the same). Note: only assuming this tool handles one assessment + , staffing_pass_nor VARCHAR + , staffing_fail_nor VARCHAR + , assessment_hurdle_id UUID references assessment_hurdle (id) ON DELETE CASCADE + , created_at TIMESTAMP DEFAULT NOW() + , updated_at TIMESTAMP DEFAULT NOW() + , CONSTRAINT unique_hurdle UNIQUE (staffing_vacancy_id,staffing_assessment_id) + , constraint unique_meta_hurdle UNIQUE(assessment_hurdle_id) +); + +CREATE TABLE IF NOT EXISTS assessment_hurdle_user ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid() + , app_user_id UUID NOT NULL REFERENCES app_user(id) ON DELETE CASCADE -- the uuid of the user + , role INT NOT NULL -- { admin: 0, hr: 1, sme: 2, all: 10} + , assessment_hurdle_id UUID NOT NULL REFERENCES assessment_hurdle(id) ON DELETE CASCADE -- the uuid of the hiringAction + , created_at TIMESTAMP DEFAULT NOW() + , updated_at TIMESTAMP DEFAULT NOW() + , CONSTRAINT user_in_assessment_hurdle UNIQUE (assessment_hurdle_id, app_user_id, role) +); + + +CREATE TABLE IF NOT EXISTS specialty ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid() + , name VARCHAR NOT NULL + , local_id VARCHAR NOT NULL -- used for mapping competencies to specialty + , assessment_hurdle_id UUID REFERENCES assessment_hurdle(id) ON DELETE CASCADE + , points_required INT DEFAULT 1 -- Default for pass/fail interviews + , created_at TIMESTAMP DEFAULT NOW() + , updated_at TIMESTAMP DEFAULT NOW() + , CONSTRAINT unique_specialty UNIQUE (name, assessment_hurdle_id) -- prevent accidental duplicate names + , CONSTRAINT unique_specialty_mapping UNIQUE (local_id, assessment_hurdle_id) -- prevent accidental duplicates specialties + +); + +CREATE TABLE IF NOT EXISTS competency ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid() + , name VARCHAR NOT NULL + , local_id VARCHAR NOT NULL + , assessment_hurdle_id UUID REFERENCES assessment_hurdle(id) ON DELETE CASCADE + , definition VARCHAR NOT NULL + , required_proficiency_definition VARCHAR + , display_type INT DEFAULT 0 -- 0 default; 1 - experience; 2 - Meets/exceeds + , screen_out boolean + , sort_order int default 0 + , created_at TIMESTAMP DEFAULT NOW() + , updated_at TIMESTAMP DEFAULT NOW() + , CONSTRAINT unique_name UNIQUE(name, assessment_hurdle_id) + , CONSTRAINT unique_local_id UNIQUE(assessment_hurdle_id, local_id) +); + +CREATE TABLE IF NOT EXISTS specialty_competencies ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid() + , specialty_id UUID REFERENCES specialty(id) ON DELETE CASCADE + , competency_id UUID references competency(id) ON DELETE CASCADE + , created_at TIMESTAMP DEFAULT NOW() + , updated_at TIMESTAMP DEFAULT NOW() + , constraint unique_combo UNIQUE(specialty_id, competency_id) +); + + +CREATE TABLE IF NOT EXISTS applicant ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid() + , name VARCHAR + , flag_type INT DEFAULT 0 -- 1: SME; 2: HR; 3: SYSTEM ERROR + , flag_message VARCHAR DEFAULT NULL + , assessment_hurdle_id UUID REFERENCES assessment_hurdle(id) ON DELETE CASCADE + , additional_note VARCHAR NULL + , created_at TIMESTAMP DEFAULT NOW() + , updated_at TIMESTAMP DEFAULT NOW() +); + +CREATE TABLE IF NOT EXISTS applicant_meta( + id UUID PRIMARY KEY DEFAULT gen_random_uuid() + , staffing_first_name VARCHAR NOT NULL + , staffing_middle_name VARCHAR + , staffing_last_name VARCHAR NOT NULL + , staffing_application_number VARCHAR -- USE THIS FOR IDENTIFICATION unique per applicant in a hiring action + , staffing_application_id VARCHAR NOT NULL -- unique per applicant in a hiring action; similiar to `staffing_application_number` + , applicant_id UUID references applicant (id) ON DELETE CASCADE + , created_at TIMESTAMP DEFAULT NOW() + , updated_at TIMESTAMP DEFAULT NOW() + , CONSTRAINT unique_applicant_id_meta UNIQUE(applicant_id) +); + + +CREATE TABLE IF NOT EXISTS application ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid() + , applicant_id UUID REFERENCES applicant(id) ON DELETE CASCADE + , specialty_id UUID REFERENCES specialty(id) ON DELETE CASCADE-- each application corresponds to a single specialty. + , created_at TIMESTAMP DEFAULT NOW() + , updated_at TIMESTAMP DEFAULT NOW() + , CONSTRAINT unique_pair UNIQUE(applicant_id, specialty_id) +); + +-- there is one application per rating combination +-- a single applicant can have multiple applications +CREATE TABLE IF NOT EXISTS application_meta ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid() + , application_id UUID REFERENCES application(id) ON DELETE CASCADE + , staffing_application_rating_id VARCHAR NOT NULL -- random id that is unique for each GS-level/role combo the applicant is applying for. ie: 6266. Different applicants applying to the same gs level/role have differnet ids. + , staffing_assessment_id VARCHAR NOT NULL -- unique per hurdle (ie: all RR have the same value). ie: '3205' + , staffing_rating_combination VARCHAR NOT NULL -- a single rating combination. ie: `2210-12(Generalist)` -- identifies the specialty + , created_at TIMESTAMP DEFAULT NOW() + , updated_at TIMESTAMP DEFAULT NOW() + , CONSTRAINT unique_application_meta UNIQUE(application_id) +); + + +CREATE TABLE IF NOT EXISTS applicant_recusals ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid() + , applicant_id UUID REFERENCeS applicant(id) ON DELETE CASCADE + , recused_evaluator_id UUID REFERENCES app_user(id) ON DELETE CASCADE + , created_at TIMESTAMP DEFAULT NOW() + , updated_at TIMESTAMP DEFAULT NOW() + , CONSTRAINT unique_combo_user UNIQUE(applicant_id, recused_evaluator_id) +); + + +CREATE TABLE IF NOT EXISTS application_evaluation ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid() + , evaluation_note VARCHAR DEFAULT NULL + , evaluator UUID references app_user(id) ON DELETE CASCADE + , approved BOOLEAN DEFAULT NULL + , approved_type INT DEFAULT 1 + , approver_id UUID REFERENCES app_user(id) ON DELETE SET NULL + , feedback_timestamp TIMESTAMP DEFAULT NULL + , application_id UUID REFERENCES application(id) ON DELETE CASCADE + , created_at TIMESTAMP DEFAULT NOW() + , updated_at TIMESTAMP DEFAULT NOW() + , CONSTRAINT unique_application_evaluation UNIQUE(application_id, evaluator) +); + + + +CREATE TABLE IF NOT EXISTS applicant_evaluation_feedback ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid() + , evaluation_feedback VARCHAR -- , application_evaluation_id UUID REFERENCES application_evaluation(id) + , applicant_id UUID REFERENCES applicant(id) ON DELETE CASCADE + , evaluator_id UUID REFERENCES app_user(id) ON DELETE CASCADE--- SME + , feedback_author_id UUID REFERENCES app_user(id) ON DELETE SET NULL-- HR + , feedback_timestamp TIMESTAMP + , created_at TIMESTAMP DEFAULT NOW() + , updated_at TIMESTAMP DEFAULT NOW() + , CONSTRAINT unique_app_evaluation_feedback UNIQUE (applicant_id, feedback_author_id, evaluator_id) --,application_evaluation_id) +); + +CREATE TABLE IF NOT EXISTS competency_selectors ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid() + , sort_order INT default 0 + , display_name VARCHAR-- e.g. [meets],[meets,exceeds], [gs13, g14, gs15, exceeds] + , point_value int default 0 -- point value 0 show under "Does not meet" + , default_text VARCHAR -- Only used for point value 0 + , competency_id UUID REFERENCES competency(id) ON DELETE CASCADE + , created_at TIMESTAMP DEFAULT NOW() + , updated_at TIMESTAMP DEFAULT NOW() + , CONSTRAINT unique_competency UNIQUE (display_name, competency_id) -- prevent accidental duplicates specialties +); + + +CREATE TABLE IF NOT EXISTS competency_evaluation ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid() + , evaluation_note VARCHAR -- this is no longer used, but leaving it for possible future use + , applicant UUID NOT NULL REFERENCES applicant(id) ON DELETE CASCADE + , evaluator UUID NOT NULL REFERENCES app_user(id) ON DELETE CASCADE + , competency_id UUID NOT NULL REFERENCES competency(id) ON DELETE CASCADE + , competency_selector_id UUID NOT NULL REFERENCES competency_selectors(id) ON DELETE CASCADE + , created_at TIMESTAMP DEFAULT NOW() + , updated_at TIMESTAMP DEFAULT NOW() + , constraint unique_competency_evaluation UNIQUE(applicant, evaluator, competency_id) +); + +-- This is a many-to-many due to overlapping competencies per specialty + +CREATE TABLE IF NOT EXISTS application_evaluation_competency ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid() + , application_evaluation_id UUID REFERENCES application_evaluation(id) ON DELETE CASCADE + , competency_evaluation_id UUID REFERENCES competency_evaluation(id) ON DELETE CASCADE + , created_at TIMESTAMP DEFAULT NOW() + , updated_at TIMESTAMP DEFAULT NOW() + , constraint unique_combo_eval UNIQUE(application_evaluation_id, competency_evaluation_id) +); + + +CREATE TABLE IF NOT EXISTS application_assignments ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid() + , evaluator_id UUID NOT NULL REFERENCES app_user(id) ON DELETE CASCADE + , applicant_id UUID NOT NULL REFERENCES applicant(id) ON DELETE CASCADE + , assessment_hurdle_id UUID NOT NULL REFERENCES assessment_hurdle(id) ON DELETE CASCADE + , active BOOLEAN + , expires TIMESTAMP DEFAULT NULL + , created_at TIMESTAMP DEFAULT NOW() + , updated_at TIMESTAMP DEFAULT NOW() + , CONSTRAINT unique_combo_eval_app UNIQUE(evaluator_id,applicant_id, assessment_hurdle_id) +); + +CREATE TABLE IF NOT EXISTS application_evaluation_audit_trail ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid() + , name VARCHAR + , email VARCHAR + , hr_review_time TIMESTAMP + , approved boolean + , evaluation_feedback VARCHAR + , evaluation_feedback_author VARCHAR + , passing boolean + , competency_evaluations VARCHAR +); + + + +CREATE TRIGGER update_with_changes BEFORE UPDATE ON app_user FOR EACH ROW EXECUTE PROCEDURE update_with_changes(); +CREATE TRIGGER update_with_changes BEFORE UPDATE ON assessment_hurdle FOR EACH ROW EXECUTE PROCEDURE update_with_changes(); +CREATE TRIGGER update_with_changes BEFORE UPDATE ON assessment_hurdle_meta FOR EACH ROW EXECUTE PROCEDURE update_with_changes(); +CREATE TRIGGER update_with_changes BEFORE UPDATE ON assessment_hurdle_user FOR EACH ROW EXECUTE PROCEDURE update_with_changes(); +CREATE TRIGGER update_with_changes BEFORE UPDATE ON specialty FOR EACH ROW EXECUTE PROCEDURE update_with_changes(); +CREATE TRIGGER update_with_changes BEFORE UPDATE ON competency FOR EACH ROW EXECUTE PROCEDURE update_with_changes(); +CREATE TRIGGER update_with_changes BEFORE UPDATE ON specialty_competencies FOR EACH ROW EXECUTE PROCEDURE update_with_changes(); +CREATE TRIGGER update_with_changes BEFORE UPDATE ON competency_selectors FOR EACH ROW EXECUTE PROCEDURE update_with_changes(); +CREATE TRIGGER update_with_changes BEFORE UPDATE ON applicant FOR EACH ROW EXECUTE PROCEDURE update_with_changes(); +CREATE TRIGGER update_with_changes BEFORE UPDATE ON applicant_meta FOR EACH ROW EXECUTE PROCEDURE update_with_changes(); +CREATE TRIGGER update_with_changes BEFORE UPDATE ON application FOR EACH ROW EXECUTE PROCEDURE update_with_changes(); +CREATE TRIGGER update_with_changes BEFORE UPDATE ON application_meta FOR EACH ROW EXECUTE PROCEDURE update_with_changes(); +CREATE TRIGGER update_with_changes BEFORE UPDATE ON applicant_recusals FOR EACH ROW EXECUTE PROCEDURE update_with_changes(); +CREATE TRIGGER update_with_changes BEFORE UPDATE ON application_evaluation FOR EACH ROW EXECUTE PROCEDURE update_with_changes(); +CREATE TRIGGER update_with_changes BEFORE UPDATE ON applicant_evaluation_feedback FOR EACH ROW EXECUTE PROCEDURE update_with_changes(); +CREATE TRIGGER update_with_changes BEFORE UPDATE ON competency_evaluation FOR EACH ROW EXECUTE PROCEDURE update_with_changes(); +CREATE TRIGGER update_with_changes BEFORE UPDATE ON application_evaluation_competency FOR EACH ROW EXECUTE PROCEDURE update_with_changes(); +CREATE TRIGGER update_with_changes BEFORE UPDATE ON application_assignments FOR EACH ROW EXECUTE PROCEDURE update_with_changes(); + diff --git a/db/migrations/11_views.sql b/db/migrations/11_views.sql new file mode 100644 index 0000000..844b190 --- /dev/null +++ b/db/migrations/11_views.sql @@ -0,0 +1,193 @@ +-- Note: +-- This file became a bit of a wasteland due to some changing reqs, specifically that we had to match failures on specific competencies... Tread carefully and read through all the comments. + +-- Single applicant's feedback status +CREATE OR REPLACE VIEW applicant_application_evaluation_notes AS +SELECT + ae.evaluator + , ae.evaluation_note + , a.applicant_id +FROM application_evaluation ae +LEFT JOIN application a ON a.id = ae.application_id +GROUP BY ae.evaluator, ae.evaluation_note, a.applicant_id; + + +-- Single application's competency's status +-- NOTE: Screen out is true for all competencies. +CREATE OR REPLACE VIEW competency_evaluation_count AS +SELECT + c.assessment_hurdle_id + , c.id competency_id + , c.screen_out + , ce.applicant + , COUNT(*) FILTER (WHERE cs.point_value <= 0) does_not_meet + , COUNT(*) FILTER (WHERE cs.point_value > 0) meets + , COUNT(DISTINCT evaluator) evaluators +FROM competency_evaluation ce +LEFT JOIN competency c on c.id = ce.competency_id +LEFT JOIN competency_selectors cs on cs.id = ce.competency_selector_id +GROUP BY c.id, ce.applicant, c.assessment_hurdle_id, c.screen_out; + +CREATE OR REPLACE VIEW competency_evaluation_count_agg AS +SELECT + assessment_hurdle_id + , competency_id + , screen_out + , applicant + , does_not_meet >= ah.evaluations_required does_not_meet + , meets >= ah.evaluations_required meets + , (does_not_meet < ah.evaluations_required AND meets < ah.evaluations_required) pending + , evaluators + , evaluations_required + FROM competency_evaluation_count + LEFT JOIN assessment_hurdle ah on ah.id = competency_evaluation_count.assessment_hurdle_id; + +-- If there are _any_ decided "does not meet" competencies, then applicant status is "does not meet" +-- After that, if there are _any_ pending competencies, then applicant is pending +-- Otherwise we need to ensure that + +CREATE OR REPLACE VIEW applicant_status AS +SELECT + applicant applicant_id + , assessment_hurdle_id + , COUNT(*) FILTER (WHERE does_not_meet IS TRUE) does_not_meet + , COUNT(*) FILTER (WHERE meets IS TRUE) meets + , COUNT(*) FILTER (WHERE pending IS TRUE) pending + , COUNT(ceca.competency_id) competencies + , MAX(evaluators) evaluators + , MAX(evaluations_required) evaluations_required + FROM competency_evaluation_count_agg ceca + GROUP BY applicant_id, assessment_hurdle_id; + +CREATE OR REPLACE VIEW applicant_status_agg AS +SELECT + applicant.id applicant_id + , applicant.assessment_hurdle_id + , CASE + WHEN SUM(does_not_meet) > 0 THEN 'does_not_meet' + WHEN SUM(pending) > 0 THEN 'pending' + WHEN SUM(meets) = SUM(competencies) THEN 'meets' + WHEN BOOL_OR(applicant_status.applicant_id IS NULL) THEN 'pending' + ELSE 'error' END status + , MAX(evaluators) evaluators + , evaluations_required + FROM applicant + LEFT JOIN applicant_status on applicant_status.applicant_id = applicant.id + GROUP BY applicant.id, evaluations_required; + +-- QUEUE override +CREATE OR REPLACE VIEW applicant_queue AS + SELECT applicant_id + , assessment_hurdle_id + , evaluators + FROM applicant_status_agg asg + WHERE status = 'pending'; + +-- REPLACED in 11_views.sql +CREATE OR REPLACE VIEW applicant_evaluation_status AS +SELECT + applicant.id applicant_id + , name + , SUM(flag_type) > 0 flagged + , CASE + WHEN bool_or(approved IS false) THEN 'pending amendment' + WHEN bool_and(approved IS null) THEN 'pending review' + WHEN bool_and(approved IS true) THEN 'complete' + ELSE 'error' end status + , COUNT(ar.id) recused +FROM applicant + LEFT JOIN application ON application.applicant_id = applicant.id + LEFT JOIN application_evaluation ae ON ae.application_id = application.id + LEFT JOIN applicant_recusals ar ON ar.applicant_id = applicant.id + GROUP BY applicant.id, name; + + +-- Applicant status metrics: +-- Name +-- Evaluations +-- Overall status (Incomplete, meets, does not meet) +-- Review status (pending evaluations, pending amendment, pending review, complete) +-- recused +-- Flagged + +-- REPLACED in 11_views.sql +CREATE OR REPLACE VIEW applicant_status_metrics AS +SELECT + asa.applicant_id + , aes.name + , asa.assessment_hurdle_id + , evaluators + , recused + , flagged + , asa.status evaluation_status + , CASE + WHEN asa.status = 'pending' THEN 'pending evaluations' + ELSE aes.status end review_status + , evaluations_required + FROM applicant_status_agg asa + LEFT JOIN applicant_evaluation_status aes on aes.applicant_id = asa.applicant_id; + +-- SME Summary +-- name +-- meets +-- does not meet +-- pending amendment +-- pending review +CREATE OR REPLACE VIEW evaluator_metrics AS +SELECT + evaluator + , ahe.assessment_hurdle_id + , au.name + , COUNT(*) FILTER (WHERE approved IS null) pending_review + , COUNT(*) FILTER (WHERE approved IS false) pending_amendment + , COUNT(*) FILTER (WHERE approved IS true) completed + , COUNT(DISTINCT(ar.applicant_id)) recusals +FROM app_user au +LEFT JOIN application_evaluation ae ON au.id = ae.evaluator +LEFT JOIN assessment_hurdle_user ahe ON ahe.app_user_id = evaluator +LEFT JOIN applicant_recusals ar ON ar.recused_evaluator_id = au.id +GROUP BY evaluator, au.name,ahe.assessment_hurdle_id; + +CREATE OR REPLACE view reviewer_metrics AS +SELECT + au.id reviewer_id + , au.name + , au.email email + , app.assessment_hurdle_id + , COUNT(*) FILTER (WHERE approved IS false) pending_amendment + , COUNT(*) FILTER (WHERE approved IS true) adjudicated +FROM application_evaluation ae +LEFT JOIN app_user au ON au.id = approver_id +LEFT JOIN application a ON a.id = ae.application_id +LEFT JOIN applicant app on app.id = a.applicant_id +group BY au.id, au.name, au.email, app.assessment_hurdle_id; + + +CREATE OR REPLACE VIEW application_evaluation_with_competencies AS +SELECT + a.name + , evaluator.email + , ae.updated_at hr_review_time + , ae.approved + , aef.evaluation_feedback + , reviewer.email evaluation_feedback_author + , BOOL_AND(cs.point_value > 0) AS passing + , STRING_AGG(CONCAT(c.name, ': ', cs.display_name, ' - ', ce.evaluation_note), ' <<<>>> ') competency_evaluations + , ae.id application_evaluation_id +FROM application_evaluation ae +LEFT JOIN application_evaluation_competency aec on aec.application_evaluation_id = ae.id +LEFT JOIN competency_evaluation ce on ce.id = aec.competency_evaluation_id +LEFT JOIN competency_selectors cs ON cs.id = ce.competency_selector_id +LEFT JOIN competency c ON c.id = ce.competency_id +LEFT JOIN application app ON app.id = ae.application_id +LEFT JOIN applicant a ON a.id = app.applicant_id +LEFT JOIN applicant_evaluation_feedback aef ON a.id = aef.applicant_id AND ae.evaluator = aef.evaluator_id +LEFT JOIN app_user evaluator ON evaluator.id = ae.evaluator +LEFT JOIN app_user reviewer ON reviewer.id = aef.feedback_author_id +GROUP BY (a.name + , evaluator.email + , ae.updated_at + , ae.approved + , aef.evaluation_feedback + , reviewer.email + , ae.id); diff --git a/db/migrations/12_functions.sql b/db/migrations/12_functions.sql new file mode 100644 index 0000000..2eb491c --- /dev/null +++ b/db/migrations/12_functions.sql @@ -0,0 +1,18 @@ +CREATE FUNCTION audit_evaluation(app_eval_id uuid) +RETURNS VOID AS $$ + begin + INSERT INTO application_evaluation_audit_trail + SELECT + gen_random_uuid() id + , name + , email + , hr_review_time + , approved + , evaluation_feedback + , evaluation_feedback_author + , passing + , competency_evaluations + FROM application_evaluation_with_competencies + WHERE application_evaluation_id = app_eval_id; + END +$$ LANGUAGE plpgsql; \ No newline at end of file diff --git a/db/migrations/20_fix_view.sql b/db/migrations/20_fix_view.sql new file mode 100644 index 0000000..212fb34 --- /dev/null +++ b/db/migrations/20_fix_view.sql @@ -0,0 +1,36 @@ +DROP VIEW applicant_status_metrics; +DROP VIEW applicant_evaluation_status; + +CREATE OR REPLACE VIEW applicant_evaluation_status AS +SELECT + applicant.id applicant_id + , name + , SUM(flag_type) > 0 flagged + , CASE + WHEN bool_or(approved IS false) THEN 'pending amendment' + WHEN bool_or(approved IS null) THEN 'pending review' + WHEN bool_and(approved IS true) THEN 'complete' + ELSE 'error' end status + , COUNT(ar.id) recused +FROM applicant + LEFT JOIN application ON application.applicant_id = applicant.id + LEFT JOIN application_evaluation ae ON ae.application_id = application.id + LEFT JOIN applicant_recusals ar ON ar.applicant_id = applicant.id + GROUP BY applicant.id, name; + + +CREATE OR REPLACE VIEW applicant_status_metrics AS +SELECT + asa.applicant_id + , aes.name + , asa.assessment_hurdle_id + , evaluators + , recused + , flagged + , asa.status evaluation_status + , CASE + WHEN asa.status = 'pending' THEN 'pending evaluations' + ELSE aes.status end review_status + , evaluations_required + FROM applicant_status_agg asa + LEFT JOIN applicant_evaluation_status aes on aes.applicant_id = asa.applicant_id; diff --git a/db/migrations/21_fix_varchars.sql b/db/migrations/21_fix_varchars.sql new file mode 100644 index 0000000..3eeb14a --- /dev/null +++ b/db/migrations/21_fix_varchars.sql @@ -0,0 +1,27 @@ +-- Must re-run 11_views.sql and 20_fix_view.sql after this +-- Have to drop views since they depend on the volumns below +DROP VIEW application_evaluation_with_competencies; +DROP VIEW evaluator_metrics; +DROP VIEW applicant_status_metrics; +DROP VIEW applicant_evaluation_status; +DROP VIEW applicant_queue; +DROP VIEW applicant_status_agg; +DROP VIEW applicant_status; +DROP VIEW competency_evaluation_count_agg; +DROP VIEW competency_evaluation_count; +DROP VIEW applicant_application_evaluation_notes; + +ALTER TABLE application_evaluation + ALTER COLUMN evaluation_note TYPE VARCHAR; +ALTER TABLE competency_evaluation + ALTER COLUMN evaluation_note TYPE VARCHAR; +ALTER TABLE competency + ALTER COLUMN definition TYPE VARCHAR; +ALTER TABLE competency + ALTER COLUMN required_proficiency_definition TYPE VARCHAR; +ALTER TABLE applicant + ALTER COLUMN additional_note TYPE VARCHAR; +ALTER TABLE applicant_evaluation_feedback + ALTER COLUMN evaluation_feedback TYPE VARCHAR; +ALTER TABLE competency_evaluation + ALTER COLUMN evaluation_note TYPE VARCHAR; diff --git a/db/migrations/22_views.sql b/db/migrations/22_views.sql new file mode 100644 index 0000000..844b190 --- /dev/null +++ b/db/migrations/22_views.sql @@ -0,0 +1,193 @@ +-- Note: +-- This file became a bit of a wasteland due to some changing reqs, specifically that we had to match failures on specific competencies... Tread carefully and read through all the comments. + +-- Single applicant's feedback status +CREATE OR REPLACE VIEW applicant_application_evaluation_notes AS +SELECT + ae.evaluator + , ae.evaluation_note + , a.applicant_id +FROM application_evaluation ae +LEFT JOIN application a ON a.id = ae.application_id +GROUP BY ae.evaluator, ae.evaluation_note, a.applicant_id; + + +-- Single application's competency's status +-- NOTE: Screen out is true for all competencies. +CREATE OR REPLACE VIEW competency_evaluation_count AS +SELECT + c.assessment_hurdle_id + , c.id competency_id + , c.screen_out + , ce.applicant + , COUNT(*) FILTER (WHERE cs.point_value <= 0) does_not_meet + , COUNT(*) FILTER (WHERE cs.point_value > 0) meets + , COUNT(DISTINCT evaluator) evaluators +FROM competency_evaluation ce +LEFT JOIN competency c on c.id = ce.competency_id +LEFT JOIN competency_selectors cs on cs.id = ce.competency_selector_id +GROUP BY c.id, ce.applicant, c.assessment_hurdle_id, c.screen_out; + +CREATE OR REPLACE VIEW competency_evaluation_count_agg AS +SELECT + assessment_hurdle_id + , competency_id + , screen_out + , applicant + , does_not_meet >= ah.evaluations_required does_not_meet + , meets >= ah.evaluations_required meets + , (does_not_meet < ah.evaluations_required AND meets < ah.evaluations_required) pending + , evaluators + , evaluations_required + FROM competency_evaluation_count + LEFT JOIN assessment_hurdle ah on ah.id = competency_evaluation_count.assessment_hurdle_id; + +-- If there are _any_ decided "does not meet" competencies, then applicant status is "does not meet" +-- After that, if there are _any_ pending competencies, then applicant is pending +-- Otherwise we need to ensure that + +CREATE OR REPLACE VIEW applicant_status AS +SELECT + applicant applicant_id + , assessment_hurdle_id + , COUNT(*) FILTER (WHERE does_not_meet IS TRUE) does_not_meet + , COUNT(*) FILTER (WHERE meets IS TRUE) meets + , COUNT(*) FILTER (WHERE pending IS TRUE) pending + , COUNT(ceca.competency_id) competencies + , MAX(evaluators) evaluators + , MAX(evaluations_required) evaluations_required + FROM competency_evaluation_count_agg ceca + GROUP BY applicant_id, assessment_hurdle_id; + +CREATE OR REPLACE VIEW applicant_status_agg AS +SELECT + applicant.id applicant_id + , applicant.assessment_hurdle_id + , CASE + WHEN SUM(does_not_meet) > 0 THEN 'does_not_meet' + WHEN SUM(pending) > 0 THEN 'pending' + WHEN SUM(meets) = SUM(competencies) THEN 'meets' + WHEN BOOL_OR(applicant_status.applicant_id IS NULL) THEN 'pending' + ELSE 'error' END status + , MAX(evaluators) evaluators + , evaluations_required + FROM applicant + LEFT JOIN applicant_status on applicant_status.applicant_id = applicant.id + GROUP BY applicant.id, evaluations_required; + +-- QUEUE override +CREATE OR REPLACE VIEW applicant_queue AS + SELECT applicant_id + , assessment_hurdle_id + , evaluators + FROM applicant_status_agg asg + WHERE status = 'pending'; + +-- REPLACED in 11_views.sql +CREATE OR REPLACE VIEW applicant_evaluation_status AS +SELECT + applicant.id applicant_id + , name + , SUM(flag_type) > 0 flagged + , CASE + WHEN bool_or(approved IS false) THEN 'pending amendment' + WHEN bool_and(approved IS null) THEN 'pending review' + WHEN bool_and(approved IS true) THEN 'complete' + ELSE 'error' end status + , COUNT(ar.id) recused +FROM applicant + LEFT JOIN application ON application.applicant_id = applicant.id + LEFT JOIN application_evaluation ae ON ae.application_id = application.id + LEFT JOIN applicant_recusals ar ON ar.applicant_id = applicant.id + GROUP BY applicant.id, name; + + +-- Applicant status metrics: +-- Name +-- Evaluations +-- Overall status (Incomplete, meets, does not meet) +-- Review status (pending evaluations, pending amendment, pending review, complete) +-- recused +-- Flagged + +-- REPLACED in 11_views.sql +CREATE OR REPLACE VIEW applicant_status_metrics AS +SELECT + asa.applicant_id + , aes.name + , asa.assessment_hurdle_id + , evaluators + , recused + , flagged + , asa.status evaluation_status + , CASE + WHEN asa.status = 'pending' THEN 'pending evaluations' + ELSE aes.status end review_status + , evaluations_required + FROM applicant_status_agg asa + LEFT JOIN applicant_evaluation_status aes on aes.applicant_id = asa.applicant_id; + +-- SME Summary +-- name +-- meets +-- does not meet +-- pending amendment +-- pending review +CREATE OR REPLACE VIEW evaluator_metrics AS +SELECT + evaluator + , ahe.assessment_hurdle_id + , au.name + , COUNT(*) FILTER (WHERE approved IS null) pending_review + , COUNT(*) FILTER (WHERE approved IS false) pending_amendment + , COUNT(*) FILTER (WHERE approved IS true) completed + , COUNT(DISTINCT(ar.applicant_id)) recusals +FROM app_user au +LEFT JOIN application_evaluation ae ON au.id = ae.evaluator +LEFT JOIN assessment_hurdle_user ahe ON ahe.app_user_id = evaluator +LEFT JOIN applicant_recusals ar ON ar.recused_evaluator_id = au.id +GROUP BY evaluator, au.name,ahe.assessment_hurdle_id; + +CREATE OR REPLACE view reviewer_metrics AS +SELECT + au.id reviewer_id + , au.name + , au.email email + , app.assessment_hurdle_id + , COUNT(*) FILTER (WHERE approved IS false) pending_amendment + , COUNT(*) FILTER (WHERE approved IS true) adjudicated +FROM application_evaluation ae +LEFT JOIN app_user au ON au.id = approver_id +LEFT JOIN application a ON a.id = ae.application_id +LEFT JOIN applicant app on app.id = a.applicant_id +group BY au.id, au.name, au.email, app.assessment_hurdle_id; + + +CREATE OR REPLACE VIEW application_evaluation_with_competencies AS +SELECT + a.name + , evaluator.email + , ae.updated_at hr_review_time + , ae.approved + , aef.evaluation_feedback + , reviewer.email evaluation_feedback_author + , BOOL_AND(cs.point_value > 0) AS passing + , STRING_AGG(CONCAT(c.name, ': ', cs.display_name, ' - ', ce.evaluation_note), ' <<<>>> ') competency_evaluations + , ae.id application_evaluation_id +FROM application_evaluation ae +LEFT JOIN application_evaluation_competency aec on aec.application_evaluation_id = ae.id +LEFT JOIN competency_evaluation ce on ce.id = aec.competency_evaluation_id +LEFT JOIN competency_selectors cs ON cs.id = ce.competency_selector_id +LEFT JOIN competency c ON c.id = ce.competency_id +LEFT JOIN application app ON app.id = ae.application_id +LEFT JOIN applicant a ON a.id = app.applicant_id +LEFT JOIN applicant_evaluation_feedback aef ON a.id = aef.applicant_id AND ae.evaluator = aef.evaluator_id +LEFT JOIN app_user evaluator ON evaluator.id = ae.evaluator +LEFT JOIN app_user reviewer ON reviewer.id = aef.feedback_author_id +GROUP BY (a.name + , evaluator.email + , ae.updated_at + , ae.approved + , aef.evaluation_feedback + , reviewer.email + , ae.id); diff --git a/db/migrations/23_fix_view.sql b/db/migrations/23_fix_view.sql new file mode 100644 index 0000000..212fb34 --- /dev/null +++ b/db/migrations/23_fix_view.sql @@ -0,0 +1,36 @@ +DROP VIEW applicant_status_metrics; +DROP VIEW applicant_evaluation_status; + +CREATE OR REPLACE VIEW applicant_evaluation_status AS +SELECT + applicant.id applicant_id + , name + , SUM(flag_type) > 0 flagged + , CASE + WHEN bool_or(approved IS false) THEN 'pending amendment' + WHEN bool_or(approved IS null) THEN 'pending review' + WHEN bool_and(approved IS true) THEN 'complete' + ELSE 'error' end status + , COUNT(ar.id) recused +FROM applicant + LEFT JOIN application ON application.applicant_id = applicant.id + LEFT JOIN application_evaluation ae ON ae.application_id = application.id + LEFT JOIN applicant_recusals ar ON ar.applicant_id = applicant.id + GROUP BY applicant.id, name; + + +CREATE OR REPLACE VIEW applicant_status_metrics AS +SELECT + asa.applicant_id + , aes.name + , asa.assessment_hurdle_id + , evaluators + , recused + , flagged + , asa.status evaluation_status + , CASE + WHEN asa.status = 'pending' THEN 'pending evaluations' + ELSE aes.status end review_status + , evaluations_required + FROM applicant_status_agg asa + LEFT JOIN applicant_evaluation_status aes on aes.applicant_id = asa.applicant_id; diff --git a/db/migrations/30_update_audit_log.sql b/db/migrations/30_update_audit_log.sql new file mode 100644 index 0000000..9506566 --- /dev/null +++ b/db/migrations/30_update_audit_log.sql @@ -0,0 +1,55 @@ +ALTER TABLE application_evaluation_audit_trail +ADD COLUMN assessment_hurdle_id UUID; + +DROP VIEW application_evaluation_with_competencies; +CREATE OR REPLACE VIEW application_evaluation_with_competencies AS +SELECT + a.name + , evaluator.email + , ae.updated_at hr_review_time + , ae.approved + , aef.evaluation_feedback + , reviewer.email evaluation_feedback_author + , BOOL_AND(cs.point_value > 0) AS passing + , STRING_AGG(CONCAT(c.name, ': ', cs.display_name, ' - ', ce.evaluation_note), ' <<<>>> ') competency_evaluations + , ae.id application_evaluation_id + , a.assessment_hurdle_id assessment_hurdle_id +FROM application_evaluation ae +LEFT JOIN application_evaluation_competency aec on aec.application_evaluation_id = ae.id +LEFT JOIN competency_evaluation ce on ce.id = aec.competency_evaluation_id +LEFT JOIN competency_selectors cs ON cs.id = ce.competency_selector_id +LEFT JOIN competency c ON c.id = ce.competency_id +LEFT JOIN application app ON app.id = ae.application_id +LEFT JOIN applicant a ON a.id = app.applicant_id +LEFT JOIN applicant_evaluation_feedback aef ON a.id = aef.applicant_id AND ae.evaluator = aef.evaluator_id +LEFT JOIN app_user evaluator ON evaluator.id = ae.evaluator +LEFT JOIN app_user reviewer ON reviewer.id = aef.feedback_author_id +GROUP BY (a.name + , evaluator.email + , ae.updated_at + , ae.approved + , aef.evaluation_feedback + , reviewer.email + , ae.id + , a.assessment_hurdle_id); + +DROP FUNCTION audit_evaluation; +CREATE FUNCTION audit_evaluation(app_eval_id uuid) +RETURNS VOID AS $$ + begin + INSERT INTO application_evaluation_audit_trail + SELECT + gen_random_uuid() id + , name + , email + , hr_review_time + , approved + , evaluation_feedback + , evaluation_feedback_author + , passing + , competency_evaluations + , assessment_hurdle_id + FROM application_evaluation_with_competencies + WHERE application_evaluation_id = app_eval_id; + END +$$ LANGUAGE plpgsql; \ No newline at end of file diff --git a/db/migrations/31_fix_eval_metric_view.sql b/db/migrations/31_fix_eval_metric_view.sql new file mode 100644 index 0000000..2ace1dd --- /dev/null +++ b/db/migrations/31_fix_eval_metric_view.sql @@ -0,0 +1,21 @@ +-- There was an issue with the view joining too many recused applicants +CREATE OR REPLACE VIEW evaluator_recusals AS +SELECT + recused_evaluator_id recuser + ,COUNT(DISTINCT(applicant_id)) recused + FROM applicant_recusals + GROUP BY recused_evaluator_id; +CREATE OR REPLACE VIEW evaluator_metrics AS +SELECT + evaluator + , ahe.assessment_hurdle_id + , au.name + , COUNT(*) FILTER (WHERE approved IS null) pending_review + , COUNT(*) FILTER (WHERE approved IS false) pending_amendment + , COUNT(*) FILTER (WHERE approved IS true) completed + , MAX(er.recused) recusals +FROM app_user au +LEFT JOIN application_evaluation ae ON au.id = ae.evaluator +LEFT JOIN assessment_hurdle_user ahe ON ahe.app_user_id = evaluator +LEFT JOIN evaluator_recusals er ON er.recuser = au.id +GROUP BY evaluator, au.name,ahe.assessment_hurdle_id, recuser; \ No newline at end of file diff --git a/db/migrations/40_temp.sql b/db/migrations/40_temp.sql new file mode 100644 index 0000000..e69de29 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..05a22f6 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,56 @@ +version: "3.7" + +services: + api: + build: + context: api + dockerfile: Dockerfile.compose + environment: + - DB_LOCAL_URI=postgres://docker_pg_user:docker_pg_pw@db:5432/docker_db + - NODE_ENV=docker + - PORT=9000 + - APP_ENV=docker + - ADMIN_TOKEN=admin + volumes: + - ./api:/opt/node_app/app + - ./db:/opt/node_app/db + - /opt/node_app/app/node_modules + ports: + - 9000:9000 + - 9229:9229 + expose: + - 9000 + - 9229 + depends_on: + - db + frontend: + container_name: FE + stdin_open: true + build: + context: frontend + dockerfile: Dockerfile + environment: + - REACT_APP_ENV=docker + - CHOKIDAR_USEPOLLING=true + - PORT=8000 + volumes: + - /opt/react_app/node_modules + - ./frontend:/opt/react_app + ports: + - 8000:8000 + expose: + - 8000 + depends_on: + - api + db: + image: postgres + environment: + - POSTGRES_DB=docker_db + - POSTGRES_USER=docker_pg_user + - POSTGRES_PASSWORD=docker_pg_pw + ports: + - 5000:5432 + expose: + - 5000 + volumes: + - ./db/migrations/:/docker-entrypoint-initdb.d/ diff --git a/frontend/.babelrc b/frontend/.babelrc new file mode 100644 index 0000000..0cfbbd8 --- /dev/null +++ b/frontend/.babelrc @@ -0,0 +1,3 @@ +{ + "plugins": ["macros"] +} diff --git a/frontend/.dockerignore b/frontend/.dockerignore new file mode 100644 index 0000000..a77c78d --- /dev/null +++ b/frontend/.dockerignore @@ -0,0 +1,6 @@ +node_modules +build +.dockerignore +Dockerfile +Dockerfile.prod +src/styles/App.css \ No newline at end of file diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..4d29575 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,23 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..6e8ff7e --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,24 @@ +FROM node:12.16.2 AS builder +RUN curl -o- -L https://yarnpkg.com/install.sh | bash + +# set working directory + +ENV WKDIR /opt/react_app +WORKDIR ${WKDIR} + + +# RUN npm install -g node-sass +RUN yarn global add react-scripts@3.4.1 +RUN yarn global add serve + +COPY package*.json . +COPY yarn.lock . + +RUN yarn install --frozen-lockfile + +ENV PATH ${WKDIR}/node_modules/.bin:$PATH + + +COPY . . + +CMD ["yarn", "start"] \ No newline at end of file diff --git a/frontend/manifest.yml b/frontend/manifest.yml new file mode 100644 index 0000000..7930227 --- /dev/null +++ b/frontend/manifest.yml @@ -0,0 +1,9 @@ +# Not in use anymore. This was used when the frontend was hosted on in its own app +# applications: +# - name: smeqa-frontend +# path: build/ +# memory: 64M +# # specify the buildpack to use +# # this can be used as an alternative if we don't have a Staticfile in the build directory +# # buildpacks: +# # - https://github.com/cloudfoundry/staticfile-buildpack diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..cc37af4 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,69 @@ +{ + "name": "resume-review-app", + "version": "0.1.0", + "private": true, + "dependencies": { + "@fortawesome/fontawesome-svg-core": "^1.2.25", + "@fortawesome/free-solid-svg-icons": "^5.11.2", + "@fortawesome/react-fontawesome": "^0.1.7", + "@reduxjs/toolkit": "^1.4.0", + "classnames": "^2.2.6", + "prop-types": "^15.7.2", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "react-html-id": "^0.1.5", + "react-loading-overlay": "^1.0.1", + "react-redux": "^7.2.1", + "react-router-dom": "^5.1.2", + "redux": "^4.0.5", + "redux-thunk": "^2.3.0" + }, + "resolutions": { + "**/react": "17.0.2", + "**/react-dom": "17.0.2" + }, + "scripts": { + "build": "npm run build:prod", + "build-css": "node-sass --include-path ./src/scss --include-path ./node_modules/uswds/dist/scss/ --output-style compressed -o src/styles/ src/scss/App.scss", + "build:prod": "REACT_APP_ENV=production npm run build:assets", + "build:staging": "REACT_APP_ENV=staging npm run build:assets", + "build:finish": "rm -rf ../api/src/client && mkdir -p ../api/src/client/ && mv build/* ../api/src/client/", + "build:assets": "npm run build-css && INLINE_RUNTIME_CHUNK=false react-scripts build && npm run build:finish", + "start": "npm-run-all -p watch-css start-js", + "start:dev": "npm-run-all -p watch-css start-js:dev ", + "start-js": "INLINE_RUNTIME_CHUNK=false react-scripts start", + "start-js:dev": "INLINE_RUNTIME_CHUNK=false REACT_APP_ENV=staging react-scripts start", + "test": "react-scripts test", + "watch-css": "npm run build-css && node-sass --include-path ./src/scss --include-path ./node_modules/uswds/dist/scss/ -o src/styles/ --watch --recursive src/scss/App.scss", + "deploy:staging": "npm run build:staging && cf push smeqa-staging", + "deploy:prod": "npm run build && cf push smeqa-prod", + "build:development": "npm run build-css && bash ./scripts/build.sh --env production" + }, + "eslintConfig": { + "extends": "react-app" + }, + "proxy": "http://api:9000", + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "devDependencies": { + "babel-plugin-macros": "^2.8.0", + "cookiejar": "^2.1.2", + "http-proxy-middleware": "^2.0.1", + "node-sass": "^6.0.1", + "npm-run-all": "^4.1.5", + "react-scripts": "^4.0.3", + "require-context.macro": "^1.2.2", + "serve": "^12.0.0", + "uswds": "^2.12.0" + } +} diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico new file mode 100644 index 0000000..01dfb80 Binary files /dev/null and b/frontend/public/favicon.ico differ diff --git a/frontend/public/img/icon-dot-gov.svg b/frontend/public/img/icon-dot-gov.svg new file mode 100644 index 0000000..3bf0478 --- /dev/null +++ b/frontend/public/img/icon-dot-gov.svg @@ -0,0 +1 @@ +icon-dot-gov \ No newline at end of file diff --git a/frontend/public/img/icon-https.svg b/frontend/public/img/icon-https.svg new file mode 100644 index 0000000..19ad04f --- /dev/null +++ b/frontend/public/img/icon-https.svg @@ -0,0 +1 @@ +icon-https \ No newline at end of file diff --git a/frontend/public/img/login-inspect.png b/frontend/public/img/login-inspect.png new file mode 100644 index 0000000..3397b16 Binary files /dev/null and b/frontend/public/img/login-inspect.png differ diff --git a/frontend/public/img/us_flag_small.png b/frontend/public/img/us_flag_small.png new file mode 100644 index 0000000..34b927b Binary files /dev/null and b/frontend/public/img/us_flag_small.png differ diff --git a/frontend/public/img/usds-logo.png b/frontend/public/img/usds-logo.png new file mode 100644 index 0000000..fc2d6a4 Binary files /dev/null and b/frontend/public/img/usds-logo.png differ diff --git a/frontend/public/index.html b/frontend/public/index.html new file mode 100644 index 0000000..3749f6b --- /dev/null +++ b/frontend/public/index.html @@ -0,0 +1,32 @@ + + + + + + + + + + + + + Resume Review Tool + + +
+ + diff --git a/frontend/public/logo192.png b/frontend/public/logo192.png new file mode 100644 index 0000000..9ad2c2b Binary files /dev/null and b/frontend/public/logo192.png differ diff --git a/frontend/public/logo512.png b/frontend/public/logo512.png new file mode 100644 index 0000000..a4e47a6 Binary files /dev/null and b/frontend/public/logo512.png differ diff --git a/frontend/public/manifest.json b/frontend/public/manifest.json new file mode 100644 index 0000000..e4c94e6 --- /dev/null +++ b/frontend/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/frontend/public/robots.txt b/frontend/public/robots.txt new file mode 100644 index 0000000..b21f088 --- /dev/null +++ b/frontend/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: / diff --git a/frontend/src/App/App.jsx b/frontend/src/App/App.jsx new file mode 100644 index 0000000..02d29fd --- /dev/null +++ b/frontend/src/App/App.jsx @@ -0,0 +1,31 @@ +import React from "react"; +import { useSelector } from "react-redux"; +import { selectUser } from "./appSlice"; +import Banner from "./commonComponents/Banner"; +import Footer from "./commonComponents/Footer"; +import Header from "./commonComponents/Header"; +import Loading from "./commonComponents/Loading"; + +const App = ({ children, isLoading, classNames }) => { + const user = useSelector(selectUser); + return ( +
+ + Skip to main content + + +
+ +
+
+ {children} +
+ +
+
+
+
+ ); +}; + +export default App; diff --git a/frontend/src/App/Assessment/Assessment.jsx b/frontend/src/App/Assessment/Assessment.jsx new file mode 100644 index 0000000..79849aa --- /dev/null +++ b/frontend/src/App/Assessment/Assessment.jsx @@ -0,0 +1,23 @@ +import React from "react"; +import Footer from "../commonComponents/Footer"; + +const Assessment = ({ children }) => { + return ( +
+
+
+ +
+ {children} +
+
+
+ ); +}; +export default Assessment; diff --git a/frontend/src/App/Assessment/AssessmentsAlert.jsx b/frontend/src/App/Assessment/AssessmentsAlert.jsx new file mode 100644 index 0000000..ea57f92 --- /dev/null +++ b/frontend/src/App/Assessment/AssessmentsAlert.jsx @@ -0,0 +1,20 @@ +import React from "react"; +import Alert from "../commonComponents/Alert"; + +const AssessmentsAlert = ({ type, title, body }) => { + return ( +
+
+ + Exit review + +
+
+ ); +}; + +export default AssessmentsAlert; diff --git a/frontend/src/App/Assessment/Competencies/DoesNotMeetJustification/index.jsx b/frontend/src/App/Assessment/Competencies/DoesNotMeetJustification/index.jsx new file mode 100644 index 0000000..eea3e9d --- /dev/null +++ b/frontend/src/App/Assessment/Competencies/DoesNotMeetJustification/index.jsx @@ -0,0 +1,113 @@ +import React from "react"; + +import { COMPETENCY_TYPES } from "../../../../constants"; +import RadioGroup from "../../../commonComponents/RadioGroup"; +import Textarea from "../../../commonComponents/Textarea"; +import Button from "../../../commonComponents/Button"; +import { + updateCompetencyDecision, + updateCompetencyEvaluationNote, + cancelCompetencyReview, + selectIsFormComplete, +} from "../../assessmentSlice"; + +import { useDispatch, useSelector } from "react-redux"; + +const DoesNotMeetJustification = ({ + id, + formattedName, + failingSelectors, + competencyType, + competency_selector_id, + evaluation_note, +}) => { + const isSubmitVisible = true; + const dispatch = useDispatch(); + const updateCompetencyDecisionHandler = (e) => { + const { value: selectorId } = e.target; + const selector = failingSelectors.find((s) => s.value === selectorId); + const note = + evaluation_note && evaluation_note.length + ? evaluation_note + : selector.defaultText || ""; + dispatch(updateCompetencyDecision({ id, selectorId })); + + dispatch(updateCompetencyEvaluationNote({ id, note })); + }; + const updateCompetencyEvaluationNoteHandler = (e) => { + const { value: note } = e.target; + dispatch(updateCompetencyEvaluationNote({ id, note })); + }; + const isFormCompleted = useSelector(selectIsFormComplete); + + const submitButton = isSubmitVisible ? ( + + +
+ It is not necessary to review all competencies. +
+
+ ) : null; + + const radioSelect = + competencyType === COMPETENCY_TYPES.DEFAULT ? ( + + + Failure to meet required level reasons + + + + + ) : null; + + const reviewLabel = "Evaluation Note"; + // competencyType === COMPETENCY_TYPES.DEFAULT + // ? "Evaluation Note" + // : "Evaluation Note"; + + return ( + +
+ {radioSelect} + + + +