diff --git a/Makefile b/Makefile index ccbc88eb55..630ecd240d 100644 --- a/Makefile +++ b/Makefile @@ -9,9 +9,6 @@ # Apply the contents of the .env to the terminal, so that the docker-compose file can use them in its builds export $(shell sed 's/=.*//' .env) -.DEFAULT : help -.PHONY : setup close clean build-backend run-backend build-web run-web database app api db-setup db-migrate db-rollback n8n-setup n8n-export clamav install test cypress lint lint-fix format format-fix help - ## ------------------------------------------------------------------------------ ## Alias Commands ## - Performs logical groups of commands for your convenience @@ -57,6 +54,13 @@ clean: ## Closes and cleans (removes) all project containers @echo "===============================================" @docker-compose -f docker-compose.yml down -v --rmi all --remove-orphans +prune: ## Deletes ALL docker artifacts (even those not associated to this project) + @echo -n "Delete ALL docker artifacts? [y/n] " && read ans && [ $${ans:-n} = y ] + @echo "===============================================" + @echo "Make: prune - deleting all docker artifacts + @echo "===============================================" + @docker system prune --all --volumes + ## ------------------------------------------------------------------------------ ## Build/Run Postgres DB Commands ## - Builds all of the SIMS postgres db projects (db, db_setup) diff --git a/api/.pipeline/config.js b/api/.pipeline/config.js index 5aced03a1f..51b4901dcf 100644 --- a/api/.pipeline/config.js +++ b/api/.pipeline/config.js @@ -83,6 +83,8 @@ const phases = { (isStaticDeployment && (staticUrls.dev || defaultHostAPP)) || `${appName}-${changeId}-af2668-dev.apps.silver.devops.gov.bc.ca`, backboneApiHost: 'https://api-dev-biohub-platform.apps.silver.devops.gov.bc.ca', + backboneIntakePath: '/api/dwc/submission/intake', + backboneIntakeEnabled: true, env: 'dev', elasticsearchURL: 'https://elasticsearch-af2668-dev.apps.silver.devops.gov.bc.ca', tz: config.timezone.api, @@ -104,6 +106,8 @@ const phases = { host: staticUrlsAPI.test, appHost: staticUrls.test, backboneApiHost: 'https://api-test-biohub-platform.apps.silver.devops.gov.bc.ca', + backboneIntakePath: '/api/dwc/submission/intake', + backboneIntakeEnabled: false, env: 'test', elasticsearchURL: 'http://es01:9200', tz: config.timezone.api, @@ -125,6 +129,8 @@ const phases = { host: staticUrlsAPI.prod, appHost: staticUrls.prod, backboneApiHost: 'https://api-biohub-platform.apps.silver.devops.gov.bc.ca', + backboneIntakePath: '/api/dwc/submission/intake', + backboneIntakeEnabled: false, env: 'prod', elasticsearchURL: 'http://es01:9200', tz: config.timezone.api, diff --git a/api/.pipeline/lib/api.deploy.js b/api/.pipeline/lib/api.deploy.js index 0f6b0b867b..ed066ba600 100644 --- a/api/.pipeline/lib/api.deploy.js +++ b/api/.pipeline/lib/api.deploy.js @@ -31,6 +31,8 @@ module.exports = (settings) => { CHANGE_ID: phases.build.changeId || changeId, APP_HOST: phases[phase].appHost, BACKBONE_API_HOST: phases[phase].backboneApiHost, + BACKBONE_INTAKE_PATH: phases[phase].backboneIntakePath, + BACKBONE_INTAKE_ENABLED: phases[phase].backboneIntakeEnabled, NODE_ENV: phases[phase].env || 'dev', ELASTICSEARCH_URL: phases[phase].elasticsearchURL, TZ: phases[phase].tz, diff --git a/api/openshift/api.bc.yaml b/api/openshift/api.bc.yaml index e9e161c74b..23d631c9d2 100644 --- a/api/openshift/api.bc.yaml +++ b/api/openshift/api.bc.yaml @@ -1,4 +1,4 @@ -apiVersion: v1 +apiVersion: template.openshift.io/v1 kind: Template metadata: creationTimestamp: null diff --git a/api/openshift/api.dc.yaml b/api/openshift/api.dc.yaml index 3139914276..7836bade2e 100644 --- a/api/openshift/api.dc.yaml +++ b/api/openshift/api.dc.yaml @@ -1,4 +1,4 @@ -apiVersion: v1 +apiVersion: template.openshift.io/v1 kind: Template metadata: resourceVersion: '' @@ -25,7 +25,14 @@ parameters: description: APP host for application frontend value: '' - name: BACKBONE_API_HOST - description: API host for BioHub Platform Backbone + required: true + description: API host for BioHub Platform Backbone. Example "https://platform.com". + - name: BACKBONE_INTAKE_PATH + required: true + description: API path for BioHub Platform Backbone DwCA submission intake endpoint. Example "/api/path/to/intake". + - name: BACKBONE_INTAKE_ENABLED + required: true + description: Controls whether or not SIMS will submit DwCA datasets to the BioHub Platform Backbone. Set to "true" to enable it, will be disabled by default otherwise. - name: CHANGE_ID description: Change id of the project. This will help to pull image stream required: true @@ -161,6 +168,10 @@ objects: value: ${APP_HOST} - name: BACKBONE_API_HOST value: ${BACKBONE_API_HOST} + - name: BACKBONE_INTAKE_PATH + value: ${BACKBONE_INTAKE_PATH} + - name: BACKBONE_INTAKE_ENABLED + value: ${BACKBONE_INTAKE_ENABLED} - name: ENABLE_FILE_VIRUS_SCAN value: ${ENABLE_FILE_VIRUS_SCAN} - name: CLAMAV_HOST diff --git a/api/package-lock.json b/api/package-lock.json index 6f5b522728..ca4d0a05ad 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -1,8 +1,12543 @@ { "name": "sims-api", "version": "0.0.0", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "sims-api", + "version": "0.0.0", + "license": "Apache-2.0", + "dependencies": { + "@elastic/elasticsearch": "~8.1.0", + "@turf/bbox": "~6.5.0", + "@turf/circle": "~6.5.0", + "@turf/helpers": "~6.5.0", + "@turf/meta": "~6.5.0", + "adm-zip": "~0.5.5", + "ajv": "~8.6.3", + "aws-sdk": "~2.742.0", + "axios": "~0.21.4", + "clamdjs": "~1.0.2", + "db-migrate": "~0.11.11", + "db-migrate-pg": "~1.2.2", + "express": "~4.17.1", + "express-openapi": "~9.3.0", + "fast-deep-equal": "~3.1.3", + "fast-json-patch": "~3.1.1", + "form-data": "~4.0.0", + "jsonpath": "~1.1.1", + "jsonwebtoken": "~8.5.1", + "jwks-rsa": "~2.0.5", + "knex": "~1.0.1", + "lodash": "~4.17.21", + "mime": "~2.5.2", + "moment": "~2.29.2", + "multer": "~1.4.3", + "pg": "~8.7.1", + "qs": "~6.10.1", + "sql-template-strings": "~2.2.2", + "swagger-ui-express": "~4.3.0", + "typescript": "~4.1.6", + "uuid": "~8.3.2", + "winston": "~3.3.3", + "xlsx": "~0.17.0", + "xml2js": "~0.4.23" + }, + "devDependencies": { + "@istanbuljs/nyc-config-typescript": "~1.0.1", + "@types/adm-zip": "~0.4.34", + "@types/chai": "~4.2.22", + "@types/express": "~4.17.13", + "@types/geojson": "~7946.0.8", + "@types/gulp": "~4.0.9", + "@types/jsonpath": "~0.2.0", + "@types/jsonwebtoken": "~8.5.5", + "@types/lodash": "~4.14.176", + "@types/mime": "~2.0.3", + "@types/mocha": "~9.0.0", + "@types/multer": "~1.4.7", + "@types/node": "~14.14.31", + "@types/pg": "~8.6.1", + "@types/sinon": "~10.0.4", + "@types/sinon-chai": "~3.2.5", + "@types/swagger-ui-express": "~4.1.3", + "@types/uuid": "~8.3.1", + "@types/xml2js": "~0.4.9", + "@types/yamljs": "~0.2.31", + "@typescript-eslint/eslint-plugin": "~4.33.0", + "@typescript-eslint/parser": "~4.33.0", + "chai": "~4.3.4", + "del": "~6.0.0", + "eslint": "~7.32.0", + "eslint-config-prettier": "~6.15.0", + "eslint-plugin-prettier": "~3.3.1", + "gulp": "~4.0.2", + "gulp-typescript": "~5.0.1", + "mocha": "~8.4.0", + "nodemon": "~2.0.14", + "npm-run-all": "~4.1.5", + "nyc": "~15.1.0", + "prettier": "~2.2.1", + "prettier-plugin-organize-imports": "~2.3.4", + "sinon": "~11.1.2", + "sinon-chai": "~3.7.0", + "ts-mocha": "~8.0.0", + "ts-node": "~10.4.0" + }, + "engines": { + "node": ">= 14.0.0", + "npm": ">= 6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, + "node_modules/@babel/core": { + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.10.tgz", + "integrity": "sha512-eTAlQKq65zHfkHZV0sIVODCPGVgoo1HdBlbSLi9CqOzuZanMv2ihzY+4paiKr1mH+XmYESMAmJ/dpZ68eN6d8w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.12.10", + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helpers": "^7.12.5", + "@babel/parser": "^7.12.10", + "@babel/template": "^7.12.7", + "@babel/traverse": "^7.12.10", + "@babel/types": "^7.12.10", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.19", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.11.tgz", + "integrity": "sha512-Ggg6WPOJtSi8yYQvLVjG8F/TlpWDlKx0OpS4Kt+xMQPs5OaGYWy+v1A+1TvxI6sAMGZpKWWoAQ1DaeQbImlItA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.12.11", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz", + "integrity": "sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA==", + "dev": true, + "dependencies": { + "@babel/helper-get-function-arity": "^7.12.10", + "@babel/template": "^7.12.7", + "@babel/types": "^7.12.11" + } + }, + "node_modules/@babel/helper-get-function-arity": { + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz", + "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==", + "dev": true, + "dependencies": { + "@babel/types": "^7.12.10" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.7.tgz", + "integrity": "sha512-DCsuPyeWxeHgh1Dus7APn7iza42i/qXqiFPWyBDdOFtvS581JQePsc1F/nD+fHrcswhLlRc2UpYS1NwERxZhHw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.12.7" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.5.tgz", + "integrity": "sha512-SR713Ogqg6++uexFRORf/+nPXMmWIn80TALu0uaFb+iQIUoR7bOC7zBWyzBs5b3tBBJXuyD0cRu1F15GyzjOWA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.12.5" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.12.1.tgz", + "integrity": "sha512-QQzehgFAZ2bbISiCpmVGfiGux8YVFXQ0abBic2Envhej22DVXV9nCFaS5hIQbkyo1AdGb+gNME2TSh3hYJVV/w==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.12.1", + "@babel/helper-replace-supers": "^7.12.1", + "@babel/helper-simple-access": "^7.12.1", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/helper-validator-identifier": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.12.1", + "@babel/types": "^7.12.1", + "lodash": "^4.17.19" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.10.tgz", + "integrity": "sha512-4tpbU0SrSTjjt65UMWSrUOPZTsgvPgGG4S8QSTNHacKzpS51IVWGDj0yCwyeZND/i+LSN2g/O63jEXEWm49sYQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.12.10" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.11.tgz", + "integrity": "sha512-q+w1cqmhL7R0FNzth/PLLp2N+scXEK/L2AHbXUyydxp828F4FEa5WcVoqui9vFRiHDQErj9Zof8azP32uGVTRA==", + "dev": true, + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.12.7", + "@babel/helper-optimise-call-expression": "^7.12.10", + "@babel/traverse": "^7.12.10", + "@babel/types": "^7.12.11" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.12.1.tgz", + "integrity": "sha512-OxBp7pMrjVewSSC8fXDFrHrBcJATOOFssZwv16F3/6Xtc138GHybBfPbm9kfiqQHKhYQrlamWILwlDCeyMFEaA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.12.1" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.11.tgz", + "integrity": "sha512-LsIVN8j48gHgwzfocYUSkO/hjYAOJqlpJEc7tGXcIm4cubjVUf8LGW6eWRyxEu7gA25q02p0rQUWoCI33HNS5g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.12.11" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "node_modules/@babel/helpers": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.5.tgz", + "integrity": "sha512-lgKGMQlKqA8meJqKsW6rUnc4MdUk35Ln0ATDqdM1a/UpARODdI4j5Y5lVfUScnSNkJcdCRAaWkspykNoFg9sJA==", + "dev": true, + "dependencies": { + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.12.5", + "@babel/types": "^7.12.5" + } + }, + "node_modules/@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "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/parser": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz", + "integrity": "sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", + "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.12.7", + "@babel/types": "^7.12.7" + } + }, + "node_modules/@babel/traverse": { + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.12.tgz", + "integrity": "sha512-s88i0X0lPy45RrLM8b9mz8RPH5FqO9G9p7ti59cToE44xFm1Q+Pjh5Gq4SXBbtb88X7Uy7pexeqRIQDDMNkL0w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.11", + "@babel/generator": "^7.12.11", + "@babel/helper-function-name": "^7.12.11", + "@babel/helper-split-export-declaration": "^7.12.11", + "@babel/parser": "^7.12.11", + "@babel/types": "^7.12.12", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "node_modules/@babel/traverse/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/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.12.12", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.12.tgz", + "integrity": "sha512-lnIX7piTxOH22xE7fDXDbSHg9MM1/6ORnafpJmov5rs0kX5g4BZxeXNJLXsMRiO0U5Rb8/FvMS6xlTnTHvxonQ==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "node_modules/@babel/types/node_modules/@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "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.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "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/@elastic/elasticsearch": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@elastic/elasticsearch/-/elasticsearch-8.1.0.tgz", + "integrity": "sha512-IiZ6u77C7oYYbUkx/YFgEJk6ZtP+QDI97VaUWiYD14xIdn/w9WJtmx/Y1sN8ov0nZzrWbqScB2Z7Pb8oxo7vqw==", + "dependencies": { + "@elastic/transport": "^8.0.2", + "tslib": "^2.3.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@elastic/elasticsearch/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/@elastic/transport": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@elastic/transport/-/transport-8.0.2.tgz", + "integrity": "sha512-OlDz3WO3pKE9vSxW4wV/mn7rYCtBmSsDwxr64h/S1Uc/zrIBXb0iUsRMSkiybXugXhjwyjqG2n1Wc7jjFxrskQ==", + "dependencies": { + "debug": "^4.3.2", + "hpagent": "^0.1.2", + "ms": "^2.1.3", + "secure-json-parse": "^2.4.0", + "tslib": "^2.3.0", + "undici": "^4.14.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@elastic/transport/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@elastic/transport/node_modules/debug/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/@elastic/transport/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/@elastic/transport/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/@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/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/@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/@eslint/eslintrc/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/@eslint/eslintrc/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/@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.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@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/nyc-config-typescript": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@istanbuljs/nyc-config-typescript/-/nyc-config-typescript-1.0.1.tgz", + "integrity": "sha512-/gz6LgVpky205LuoOfwEZmnUtaSmdk0QIMcNFj9OvxhiMhPpKftMgZmGN7jNj7jR+lr8IB1Yks3QSSSNSxfoaQ==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "nyc": ">=15", + "source-map-support": "*", + "ts-node": "*" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", + "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", + "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.4", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz", + "integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz", + "integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.4", + "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": "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/@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": "7.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", + "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-6.0.2.tgz", + "integrity": "sha512-jxPRPp9n93ci7b8hMfJOFDPRLFYadN6FSpeROFTR4UNF4i5b+EK6m4QXPO46BDhFgRy1JuS87zAnFOzCUwMJcQ==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, + "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/@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/@turf/bbox": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/bbox/-/bbox-6.5.0.tgz", + "integrity": "sha512-RBbLaao5hXTYyyg577iuMtDB8ehxMlUqHEJiMs8jT1GHkFhr6sYre3lmLsPeYEi/ZKj5TP5tt7fkzNdJ4GIVyw==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/circle": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/circle/-/circle-6.5.0.tgz", + "integrity": "sha512-oU1+Kq9DgRnoSbWFHKnnUdTmtcRUMmHoV9DjTXu9vOLNV5OWtAAh1VZ+mzsioGGzoDNT/V5igbFOkMfBQc0B6A==", + "dependencies": { + "@turf/destination": "^6.5.0", + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/destination": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/destination/-/destination-6.5.0.tgz", + "integrity": "sha512-4cnWQlNC8d1tItOz9B4pmJdWpXqS0vEvv65bI/Pj/genJnsL7evI0/Xw42RvEGROS481MPiU80xzvwxEvhQiMQ==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/helpers": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-6.5.0.tgz", + "integrity": "sha512-VbI1dV5bLFzohYYdgqwikdMVpe7pJ9X3E+dlr425wa2/sMJqYDhTO++ec38/pcPvPE6oD9WEEeU3Xu3gza+VPw==", + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/invariant": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/invariant/-/invariant-6.5.0.tgz", + "integrity": "sha512-Wv8PRNCtPD31UVbdJE/KVAWKe7l6US+lJItRR/HOEW3eh+U/JwRCSUl/KZ7bmjM/C+zLNoreM2TU6OoLACs4eg==", + "dependencies": { + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/meta": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/meta/-/meta-6.5.0.tgz", + "integrity": "sha512-RrArvtsV0vdsCBegoBtOalgdSOfkBrTJ07VkpiCnq/491W67hnMWmDu7e6Ztw0C3WldRYTXkg3SumfdzZxLBHA==", + "dependencies": { + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@types/adm-zip": { + "version": "0.4.34", + "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.4.34.tgz", + "integrity": "sha512-8ToYLLAYhkRfcmmljrKi22gT2pqu7hGMDtORP1emwIEGmgUTZOsaDjzWFzW5N2frcFRz/50CWt4zA1CxJ73pmQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "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==", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/chai": { + "version": "4.2.22", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.22.tgz", + "integrity": "sha512-tFfcE+DSTzWAgifkjik9AySNqIyNoYwmR+uecPwwD/XRNfvOjmC/FjCxpiUGDkDVDphPfCUecSQVFw+lN3M3kQ==", + "dev": true + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/expect": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/@types/expect/-/expect-1.20.4.tgz", + "integrity": "sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==", + "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==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-jwt": { + "version": "0.0.42", + "resolved": "https://registry.npmjs.org/@types/express-jwt/-/express-jwt-0.0.42.tgz", + "integrity": "sha512-WszgUddvM1t5dPpJ3LhWNH8kfNN8GPIBrAGxgIYXVCEGx6Bx4A036aAuf/r5WH9DIEdlmp7gHOYvSM6U87B0ag==", + "dependencies": { + "@types/express": "*", + "@types/express-unless": "*" + } + }, + "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==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "node_modules/@types/express-unless": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@types/express-unless/-/express-unless-0.5.2.tgz", + "integrity": "sha512-Q74UyYRX/zIgl1HSp9tUX2PlG8glkVm+59r7aK4KGKzC5jqKIOX6rrVLRQrzpZUQ84VukHtRoeAuon2nIssHPQ==", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/geojson": { + "version": "7946.0.8", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz", + "integrity": "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA==", + "dev": true + }, + "node_modules/@types/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", + "dev": true, + "dependencies": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "node_modules/@types/glob-stream": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@types/glob-stream/-/glob-stream-6.1.1.tgz", + "integrity": "sha512-AGOUTsTdbPkRS0qDeyeS+6KypmfVpbT5j23SN8UPG63qjKXNKjXn6V9wZUr8Fin0m9l8oGYaPK8b2WUMF8xI1A==", + "dev": true, + "dependencies": { + "@types/glob": "*", + "@types/node": "*" + } + }, + "node_modules/@types/gulp": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/gulp/-/gulp-4.0.9.tgz", + "integrity": "sha512-zzT+wfQ8uwoXjDhRK9Zkmmk09/fbLLmN/yDHFizJiEKIve85qutOnXcP/TM2sKPBTU+Jc16vfPbOMkORMUBN7Q==", + "dev": true, + "dependencies": { + "@types/undertaker": "*", + "@types/vinyl-fs": "*", + "chokidar": "^3.3.1" + } + }, + "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/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "dev": true, + "optional": true + }, + "node_modules/@types/jsonpath": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@types/jsonpath/-/jsonpath-0.2.0.tgz", + "integrity": "sha512-v7qlPA0VpKUlEdhghbDqRoKMxFB3h3Ch688TApBJ6v+XLDdvWCGLJIYiPKGZnS6MAOie+IorCfNYVHOPIHSWwQ==", + "dev": true + }, + "node_modules/@types/jsonwebtoken": { + "version": "8.5.5", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.5.tgz", + "integrity": "sha512-OGqtHQ7N5/Ap/TUwO6IgHDuLiAoTmHhGpNvgkCm/F4N6pKzx/RBSfr2OXZSwC6vkfnsEdb6+7DNZVtiXiwdwFw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/lodash": { + "version": "4.14.176", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.176.tgz", + "integrity": "sha512-xZmuPTa3rlZoIbtDUyJKZQimJV3bxCmzMIO2c9Pz9afyDro6kr7R79GwcB6mRhuoPmV2p1Vb66WOJH7F886WKQ==", + "dev": true + }, + "node_modules/@types/mime": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz", + "integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==", + "dev": true + }, + "node_modules/@types/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "dev": true + }, + "node_modules/@types/mocha": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.0.0.tgz", + "integrity": "sha512-scN0hAWyLVAvLR9AyW7HoFF5sJZglyBsbPuHO4fv7JRvfmPBMfp1ozWqOf/e4wwPNxezBZXRfWzMb6iFLgEVRA==", + "dev": true + }, + "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.14.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.45.tgz", + "integrity": "sha512-DssMqTV9UnnoxDWu959sDLZzfvqCF0qDNRjaWeYSui9xkFe61kKo4l1TWNTQONpuXEm+gLMRvdlzvNHBamzmEw==" + }, + "node_modules/@types/pg": { + "version": "8.6.1", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.1.tgz", + "integrity": "sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==", + "dev": true, + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" + }, + "node_modules/@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" + }, + "node_modules/@types/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==", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static/node_modules/@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" + }, + "node_modules/@types/sinon": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.4.tgz", + "integrity": "sha512-fOYjrxQv8zJsqOY6V6ecP4eZhQBxtY80X0er1VVnUIAIZo74jHm8e1vguG5Yt4Iv8W2Wr7TgibB8MfRe32k9pA==", + "dev": true, + "dependencies": { + "@sinonjs/fake-timers": "^7.1.0" + } + }, + "node_modules/@types/sinon-chai": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@types/sinon-chai/-/sinon-chai-3.2.5.tgz", + "integrity": "sha512-bKQqIpew7mmIGNRlxW6Zli/QVyc3zikpGzCa797B/tRnD9OtHvZ/ts8sYXV+Ilj9u3QRaUEM8xrjgd1gwm1BpQ==", + "dev": true, + "dependencies": { + "@types/chai": "*", + "@types/sinon": "*" + } + }, + "node_modules/@types/swagger-ui-express": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.3.tgz", + "integrity": "sha512-jqCjGU/tGEaqIplPy3WyQg+Nrp6y80DCFnDEAvVKWkJyv0VivSSDCChkppHRHAablvInZe6pijDFMnavtN0vqA==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/undertaker": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/undertaker/-/undertaker-1.2.7.tgz", + "integrity": "sha512-xuY7nBwo1zSRoY2aitp/HArHfTulFAKql2Fr4b4mWbBBP+F50n7Jm6nwISTTMaDk2xvl92O10TTejVF0Q9mInw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/undertaker-registry": "*", + "async-done": "~1.3.2" + } + }, + "node_modules/@types/undertaker-registry": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/undertaker-registry/-/undertaker-registry-1.0.1.tgz", + "integrity": "sha512-Z4TYuEKn9+RbNVk1Ll2SS4x1JeLHecolIbM/a8gveaHsW0Hr+RQMraZACwTO2VD7JvepgA6UO1A1VrbktQrIbQ==", + "dev": true + }, + "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/vinyl": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.6.tgz", + "integrity": "sha512-ayJ0iOCDNHnKpKTgBG6Q6JOnHTj9zFta+3j2b8Ejza0e4cvRyMn0ZoLEmbPrTHe5YYRlDYPvPWVdV4cTaRyH7g==", + "dev": true, + "dependencies": { + "@types/expect": "^1.20.4", + "@types/node": "*" + } + }, + "node_modules/@types/vinyl-fs": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@types/vinyl-fs/-/vinyl-fs-2.4.12.tgz", + "integrity": "sha512-LgBpYIWuuGsihnlF+OOWWz4ovwCYlT03gd3DuLwex50cYZLmX3yrW+sFF9ndtmh7zcZpS6Ri47PrIu+fV+sbXw==", + "dev": true, + "dependencies": { + "@types/glob-stream": "*", + "@types/node": "*", + "@types/vinyl": "*" + } + }, + "node_modules/@types/xml2js": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.9.tgz", + "integrity": "sha512-CHiCKIihl1pychwR2RNX5mAYmJDACgFVCMT5OArMaO3erzwXVcBqPcusr+Vl8yeeXukxZqtF8mZioqX+mpjjdw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yamljs": { + "version": "0.2.31", + "resolved": "https://registry.npmjs.org/@types/yamljs/-/yamljs-0.2.31.tgz", + "integrity": "sha512-QcJ5ZczaXAqbVD3o8mw/mEBhRvO5UAdTtbvgwL/OgoWubvNBh6/MxLBAigtcgIFaq3shon9m3POIxQaLQt4fxQ==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz", + "integrity": "sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg==", + "dev": true, + "dependencies": { + "@typescript-eslint/experimental-utils": "4.33.0", + "@typescript-eslint/scope-manager": "4.33.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/eslint-plugin/node_modules/debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@typescript-eslint/experimental-utils": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.33.0.tgz", + "integrity": "sha512-zeQjOoES5JFjTnAhI5QY7ZviczMzDptls15GFsI6jyUOq0kOf9+WonkhtlIhh0RgHRnqj5gdNxW5j1EvAyYg6Q==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.7", + "@typescript-eslint/scope-manager": "4.33.0", + "@typescript-eslint/types": "4.33.0", + "@typescript-eslint/typescript-estree": "4.33.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.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.33.0.tgz", + "integrity": "sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "4.33.0", + "@typescript-eslint/types": "4.33.0", + "@typescript-eslint/typescript-estree": "4.33.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/parser/node_modules/debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.33.0.tgz", + "integrity": "sha512-5IfJHpgTsTZuONKbODctL4kKuQje/bzBRkwHE8UOZ4f89Zeddg+EGZs8PD8NcN4LdM3ygHWYB3ukPAYjvl/qbQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "4.33.0", + "@typescript-eslint/visitor-keys": "4.33.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.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.33.0.tgz", + "integrity": "sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ==", + "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.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz", + "integrity": "sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "4.33.0", + "@typescript-eslint/visitor-keys": "4.33.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/typescript-estree/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/@typescript-eslint/typescript-estree/node_modules/debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "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.6.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/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/@typescript-eslint/typescript-estree/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/@typescript-eslint/typescript-estree/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/typescript-estree/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/@typescript-eslint/typescript-estree/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/typescript-estree/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/@typescript-eslint/typescript-estree/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/typescript-estree/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/@typescript-eslint/typescript-estree/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz", + "integrity": "sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "4.33.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/@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "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-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": "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/adler-32": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.2.0.tgz", + "integrity": "sha1-aj5r8KY5ALoVZSgIyxXGgT0aXyU=", + "dependencies": { + "exit-on-epipe": "~1.0.1", + "printj": "~1.1.0" + }, + "bin": { + "adler32": "bin/adler32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/adm-zip": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.5.tgz", + "integrity": "sha512-IWwXKnCbirdbyXSfUDvCCrmYrOHANRZcc8NcRrvTlIApdl7PwE9oGcsYvNeJPAVY1M+70b4PxXGKIf8AEuiQ6w==", + "engines": { + "node": ">=6.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==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "8.6.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", + "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", + "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/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "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-gray": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", + "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", + "dev": true, + "dependencies": { + "ansi-wrap": "0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "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==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", + "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", + "dev": true, + "dependencies": { + "buffer-equal": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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/append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "dev": true, + "dependencies": { + "default-require-extensions": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "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==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-filter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", + "integrity": "sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4=", + "dev": true, + "dependencies": { + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", + "integrity": "sha1-Onc0X/wc814qkYJWAfnljy4kysQ=", + "dev": true, + "dependencies": { + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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-initial": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", + "integrity": "sha1-L6dLJnOTccOUe9enrcc74zSz15U=", + "dev": true, + "dependencies": { + "array-slice": "^1.0.0", + "is-number": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-initial/node_modules/is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-last": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", + "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", + "dev": true, + "dependencies": { + "is-number": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-last/node_modules/is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-slice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-sort": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", + "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", + "dev": true, + "dependencies": { + "default-compare": "^1.0.0", + "get-value": "^2.0.6", + "kind-of": "^5.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-sort/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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/array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=" + }, + "node_modules/async-done": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", + "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.2", + "process-nextick-args": "^2.0.0", + "stream-exhaust": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "dev": true + }, + "node_modules/async-settle": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", + "integrity": "sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs=", + "dev": true, + "dependencies": { + "async-done": "^1.2.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true, + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/aws-sdk": { + "version": "2.742.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.742.0.tgz", + "integrity": "sha512-zntDB0BpMn/y+B4RQvXuqY8DmJDYPkeFjZ6BbZ6vdNrsdB5TRz8p53ats4D3mLG068RB4M4AmVioFnU69nDXyQ==", + "dependencies": { + "buffer": "4.9.2", + "events": "1.1.1", + "ieee754": "1.1.13", + "jmespath": "0.15.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "uuid": "3.3.2", + "xml2js": "0.4.19" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/aws-sdk/node_modules/uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "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/aws-sdk/node_modules/xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + } + }, + "node_modules/aws-sdk/node_modules/xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "dependencies": { + "follow-redirects": "^1.14.0" + } + }, + "node_modules/bach": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", + "integrity": "sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA=", + "dev": true, + "dependencies": { + "arr-filter": "^1.1.1", + "arr-flatten": "^1.0.1", + "arr-map": "^2.0.0", + "array-each": "^1.0.0", + "array-initial": "^1.0.0", + "array-last": "^1.1.1", + "async-done": "^1.2.2", + "async-settle": "^1.0.0", + "now-and-later": "^2.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "node_modules/base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "dependencies": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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/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/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "optional": true, + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "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/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/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/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/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/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/boxen/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/boxen/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/boxen/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/boxen/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/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "node_modules/buffer-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", + "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, + "node_modules/buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "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/busboy/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/busboy/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/busboy/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/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/cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "dependencies": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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/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/cacheable-request/node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-request/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==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, + "dependencies": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.0.tgz", + "integrity": "sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.0" + }, + "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==", + "engines": { + "node": ">=6" + } + }, + "node_modules/cfb": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.1.tgz", + "integrity": "sha512-wT2ScPAFGSVy7CY+aauMezZBnNrfnaLSrxHUHdea+Td/86vrk6ZquggV+ssBR88zNs0OnBkL2+lf9q0K+zVGzQ==", + "dependencies": { + "adler-32": "~1.3.0", + "crc-32": "~1.2.0", + "printj": "~1.3.0" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/cfb/node_modules/adler-32": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.0.tgz", + "integrity": "sha512-f5nltvjl+PRUh6YNfUstRaXwJxtfnKEWhAWWlmKvh+Y3J2+98a0KKVYDEhz6NdKGqswLhjNGznxfSsZGOvOd9g==", + "dependencies": { + "printj": "~1.2.2" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/cfb/node_modules/adler-32/node_modules/printj": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/printj/-/printj-1.2.3.tgz", + "integrity": "sha512-sanczS6xOJOg7IKDvi4sGOUOe7c1tsEzjwlLFH/zgwx/uyImVM9/rgBkc8AfiQa/Vg54nRd8mkm9yI7WV/O+WA==", + "bin": { + "printj": "bin/printj.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/cfb/node_modules/printj": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/printj/-/printj-1.3.0.tgz", + "integrity": "sha512-017o8YIaz8gLhaNxRB9eBv2mWXI2CtzhPJALnQTP+OPpuUfP0RMWqr/mHCzqVeu1AQxfzSfAtAq66vKB8y7Lzg==", + "bin": { + "printj": "bin/printj.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/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/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true, + "engines": { + "node": "*" + } + }, + "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/chokidar/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/chokidar/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/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chokidar/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/chokidar/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/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/clamdjs": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clamdjs/-/clamdjs-1.0.2.tgz", + "integrity": "sha512-gVnX5ySMULvwYL2ykZQnP4UK4nIK7ftG6z015drJyOFgWpsqXt1Hcq4fMyPwM8LLsxfgfYKLiZi288xuTfmZBQ==" + }, + "node_modules/class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "dependencies": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-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": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "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=", + "dev": true, + "dependencies": { + "mimic-response": "^1.0.0" + } + }, + "node_modules/clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "node_modules/cloneable-readable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", + "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" + } + }, + "node_modules/cloneable-readable/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==", + "dev": true, + "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/cloneable-readable/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==", + "dev": true + }, + "node_modules/cloneable-readable/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==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/codepage": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", + "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/collection-map": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", + "integrity": "sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw=", + "dev": true, + "dependencies": { + "arr-map": "^2.0.2", + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "dependencies": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "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.5.4", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.4.tgz", + "integrity": "sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw==", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true, + "bin": { + "color-support": "bin.js" + } + }, + "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/colorette": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz", + "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==" + }, + "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==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "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/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "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/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/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/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/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-disposition/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/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.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/convert-source-map/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==", + "dev": true + }, + "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-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "node_modules/copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/copy-props": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.5.tgz", + "integrity": "sha512-XBlx8HSqrT0ObQwmSzM7WE5k8FxTV75h1DX1Z3n6NhQ/UYYAvInWYmG06vFt7hQZArE2fuO62aihiWIVQwh1sw==", + "dev": true, + "dependencies": { + "each-props": "^1.3.2", + "is-plain-object": "^5.0.0" + } + }, + "node_modules/copy-props/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "node_modules/crc-32": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.0.tgz", + "integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==", + "dependencies": { + "exit-on-epipe": "~1.0.1", + "printj": "~1.1.0" + }, + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "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/cross-spawn/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/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/cycle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", + "integrity": "sha1-IegLK+hYD5i0aPN5QwZisEbDStI=", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dev": true, + "dependencies": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "node_modules/db-migrate": { + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/db-migrate/-/db-migrate-0.11.11.tgz", + "integrity": "sha512-GHZodjB5hXRy+76ZIb9z0OrUn0qSeGfvS0cCfyzPeFCBZ1YU9o9HUBQ8pUT+v/fJ9+a29eRz2xQsLfccXZtf8g==", + "dependencies": { + "balanced-match": "^1.0.0", + "bluebird": "^3.1.1", + "db-migrate-shared": "^1.2.0", + "deep-extend": "^0.6.0", + "dotenv": "^5.0.1", + "final-fs": "^1.6.0", + "inflection": "^1.10.0", + "mkdirp": "~0.5.0", + "parse-database-url": "~0.3.0", + "prompt": "^1.0.0", + "rc": "^1.2.8", + "resolve": "^1.1.6", + "semver": "^5.3.0", + "tunnel-ssh": "^4.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "db-migrate": "bin/db-migrate" + }, + "engines": { + "node": ">=8.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-db-migrate" + } + }, + "node_modules/db-migrate-base": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/db-migrate-base/-/db-migrate-base-2.3.0.tgz", + "integrity": "sha512-mxaCkSe7JC2uksvI/rKs+wOQGBSZ6B87xa4b3i+QhB+XRBpGdpMzldKE6INf+EnM6kwhbIPKjyJZgyxui9xBfQ==", + "dependencies": { + "bluebird": "^3.1.1" + } + }, + "node_modules/db-migrate-pg": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/db-migrate-pg/-/db-migrate-pg-1.2.2.tgz", + "integrity": "sha512-+rgrhGNWC2SzcfweopyZqOQ1Igz1RVFMUZwUs6SviHpOUzFwb0NZWkG0pw1GaO+JxTxS7VJjckUWkOwZbVYVag==", + "dependencies": { + "bluebird": "^3.1.1", + "db-migrate-base": "^2.3.0", + "pg": "^8.0.3", + "semver": "^5.0.3" + } + }, + "node_modules/db-migrate-shared": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/db-migrate-shared/-/db-migrate-shared-1.2.0.tgz", + "integrity": "sha512-65k86bVeHaMxb2L0Gw3y5V+CgZSRwhVQMwDMydmw5MvIpHHwD6SmBciqIwHsZfzJ9yzV/yYhdRefRM6FV5/siw==" + }, + "node_modules/db-migrate/node_modules/dotenv": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-5.0.1.tgz", + "integrity": "sha512-4As8uPrjfwb7VXC+WnLCbXK7y+Ueb2B3zgNCePYfhxS1PYeaO1YTeplffTEcbfLhvFNGLAz90VvJs9yomG7bow==", + "engines": { + "node": ">=4.6.0" + } + }, + "node_modules/debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "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/deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/deep-equal": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.2.2.tgz", + "integrity": "sha1-hLdFiW80xoTpjyzg5Cq69Du6AX0=" + }, + "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==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" + }, + "node_modules/default-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", + "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", + "dev": true, + "dependencies": { + "kind-of": "^5.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-compare/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-require-extensions": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", + "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", + "dev": true, + "dependencies": { + "strip-bom": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/default-require-extensions/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/default-resolution": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", + "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "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/define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "dependencies": { + "object-keys": "^1.0.12" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-property/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-property/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-property/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/del": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-6.0.0.tgz", + "integrity": "sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ==", + "dev": true, + "dependencies": { + "globby": "^11.0.1", + "graceful-fs": "^4.2.4", + "is-glob": "^4.0.1", + "is-path-cwd": "^2.2.0", + "is-path-inside": "^3.0.2", + "p-map": "^4.0.0", + "rimraf": "^3.0.2", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/del/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/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "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-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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/dicer/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/dicer/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/dicer/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/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/difunc": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/difunc/-/difunc-0.0.4.tgz", + "integrity": "sha512-zBiL4ALDmviHdoLC0g0G6wVme5bwAow9WfhcZLLopXCAWgg3AEf7RYTs2xugszIGulRHzEVDF/SHl9oyQU07Pw==", + "dependencies": { + "esprima": "^4.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/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/duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "dev": true + }, + "node_modules/duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "node_modules/duplexify/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==", + "dev": true, + "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/duplexify/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==", + "dev": true + }, + "node_modules/duplexify/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==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/each-props": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", + "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.1", + "object.defaults": "^1.1.0" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "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==", + "dev": true, + "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/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/error-ex/node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "node_modules/es-abstract": { + "version": "1.18.0-next.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", + "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", + "dev": true, + "dependencies": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es5-ext": { + "version": "0.10.53", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", + "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", + "dev": true, + "dependencies": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.3", + "next-tick": "~1.0.0" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "dev": true, + "dependencies": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "node_modules/es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "dev": true, + "dependencies": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "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": "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/escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=4.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "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=", + "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==", + "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=", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/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==", + "optional": true, + "engines": { + "node": ">=0.10.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=", + "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": "6.15.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.15.0.tgz", + "integrity": "sha512-a1+kOYLR8wMGustcgAjdydMsQ2A/2ipRPwRKUmfYaSxc9ZPcrku080Ctl6zrZzZNs/U82MjSv+qKREkoq3bJaw==", + "dev": true, + "dependencies": { + "get-stdin": "^6.0.0" + }, + "bin": { + "eslint-config-prettier-check": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=3.14.1" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.3.1.tgz", + "integrity": "sha512-Rq3jkcFY8RYeQLgk2cCwuc0P7SEFwDravPhsJZOQ5N4YI4DSg50NyqJ/9gdZHzQlHf8MvafSesbNJCcP/FF6pQ==", + "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/@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/eslint/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/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/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/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/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/eslint/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/eslint/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/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/eslint/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/esm": { + "version": "3.2.25", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", + "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", + "engines": { + "node": ">=6" + } + }, + "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==", + "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.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/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.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/exit-on-epipe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", + "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "dependencies": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/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/expand-brackets/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "dev": true, + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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-normalize-query-params-middleware": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/express-normalize-query-params-middleware/-/express-normalize-query-params-middleware-0.5.1.tgz", + "integrity": "sha1-2+HoE5rssjT7attcAFnHXblzPSo=" + }, + "node_modules/express-openapi": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/express-openapi/-/express-openapi-9.3.0.tgz", + "integrity": "sha512-92H8nuvO1vVMutapDqQXESOxFnaC4/tZAXSi7kJMD+xWXZwNwmuinCxbfQc7JyUY6Y3+vjFXqJ7xeTCpsUhSiA==", + "dependencies": { + "express-normalize-query-params-middleware": "^0.5.0", + "openapi-framework": "^9.3.0", + "openapi-types": "^9.3.0" + } + }, + "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/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/express/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/ext": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", + "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", + "dev": true, + "dependencies": { + "type": "^2.0.0" + } + }, + "node_modules/ext/node_modules/type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.1.0.tgz", + "integrity": "sha512-G9absDWvhAWCV2gmF1zKud3OyC61nZDwWvBL2DApaVFogI07CprggiQAOOjvp2NRjYWFzPyu7vwtDrQFq8jeSA==", + "dev": true + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extend-shallow/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "dependencies": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=", + "engines": { + "node": "> 0.1.90" + } + }, + "node_modules/fancy-log": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", + "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", + "dev": true, + "dependencies": { + "ansi-gray": "^0.1.1", + "color-support": "^1.1.3", + "parse-node-version": "^1.0.0", + "time-stamp": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-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.4", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz", + "integrity": "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2", + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fast-glob/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/fast-glob/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/fast-glob/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/fast-glob/node_modules/micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "dependencies": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fast-glob/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/fast-json-patch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz", + "integrity": "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==" + }, + "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=" + }, + "node_modules/fast-safe-stringify": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", + "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" + }, + "node_modules/fastq": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.10.0.tgz", + "integrity": "sha512-NL2Qc5L3iQEsyYzweq7qfgy5OtXCmGzGvhElGEd/SoFWEMOEczNh5s5ocaF01HDetxz+p8ecjNPA6cZxxIHmzA==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fecha": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.0.tgz", + "integrity": "sha512-aN3pcx/DSmtyoovUudctc8+6Hl4T+hI9GBBHLjA76jdZl7+b1sgh5g4k+u/GL3dTy1/pnYzKp69FpJ0OicE3Wg==" + }, + "node_modules/fflate": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.1.tgz", + "integrity": "sha512-VYM2Xy1gSA5MerKzCnmmuV2XljkpKwgJBKezW+495TTnTCh1x5HcYa1aH8wRU/MfTGhW4ziXqgwprgQUVl3Ohw==" + }, + "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-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true, + "optional": true + }, + "node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/final-fs": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/final-fs/-/final-fs-1.6.1.tgz", + "integrity": "sha1-1tzZLvb+T+jAer1WjHE1YQ7eMjY=", + "dependencies": { + "node-fs": "~0.1.5", + "when": "~2.0.1" + } + }, + "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/find-cache-dir": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", + "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "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==", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/findup-sync": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", + "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", + "dev": true, + "dependencies": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/fined": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", + "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", + "dev": true, + "dependencies": { + "expand-tilde": "^2.0.2", + "is-plain-object": "^2.0.3", + "object.defaults": "^1.1.0", + "object.pick": "^1.2.0", + "parse-filepath": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/flagged-respawn": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", + "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "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/flat-cache/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/flatted": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", + "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", + "dev": true + }, + "node_modules/flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + } + }, + "node_modules/flush-write-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==", + "dev": true, + "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/flush-write-stream/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==", + "dev": true + }, + "node_modules/flush-write-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==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "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/follow-redirects": { + "version": "1.14.8", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", + "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "dev": true, + "dependencies": { + "for-in": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/frac": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", + "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "dependencies": { + "map-cache": "^0.2.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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/fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "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/fs-mkdirp-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", + "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.11", + "through2": "^2.0.3" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/fs-routes": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/fs-routes/-/fs-routes-9.0.3.tgz", + "integrity": "sha512-Y5tkylY9fQ1jm11FdJoptzqIG3OyzqrOF16W5odNlIdqFqb2355IbNB3jQkE+C268mSShLmIur8ynYCgL/Yg/g==", + "peerDependencies": { + "glob": ">=7.1.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=" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/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==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.1.tgz", + "integrity": "sha512-ZnWP+AmS1VUaLgTRy47+zKtjTxz+0xMpx3I52i+aalBK1QP19ggLF3Db89KJX7kjfOfP2eoa01qc++GwPgufPg==", + "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-stdin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", + "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "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/get-stream/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==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/getopts": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/getopts/-/getopts-2.3.0.tgz", + "integrity": "sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA==" + }, + "node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-stream": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", + "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", + "dev": true, + "dependencies": { + "extend": "^3.0.0", + "glob": "^7.1.1", + "glob-parent": "^3.1.0", + "is-negated-glob": "^1.0.0", + "ordered-read-streams": "^1.0.0", + "pumpify": "^1.3.5", + "readable-stream": "^2.1.5", + "remove-trailing-separator": "^1.0.1", + "to-absolute-glob": "^2.0.0", + "unique-stream": "^2.0.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/glob-stream/node_modules/glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "dependencies": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "node_modules/glob-stream/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-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==", + "dev": true, + "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/glob-stream/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==", + "dev": true + }, + "node_modules/glob-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==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/glob-watcher": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.5.tgz", + "integrity": "sha512-zOZgGGEHPklZNjZQaZ9f41i7F2YwE+tS5ZHrDhbBCk3stwahn5vQxnFmBJZHoYdusR6R1bLSXeGUy/BhctwKzw==", + "dev": true, + "dependencies": { + "anymatch": "^2.0.0", + "async-done": "^1.2.0", + "chokidar": "^2.0.0", + "is-negated-glob": "^1.0.0", + "just-debounce": "^1.0.0", + "normalize-path": "^3.0.0", + "object.defaults": "^1.1.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/glob-watcher/node_modules/anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "dependencies": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "node_modules/glob-watcher/node_modules/anymatch/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-watcher/node_modules/binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-watcher/node_modules/chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "deprecated": "Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies", + "dev": true, + "dependencies": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + }, + "optionalDependencies": { + "fsevents": "^1.2.7" + } + }, + "node_modules/glob-watcher/node_modules/fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/glob-watcher/node_modules/glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "dependencies": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "node_modules/glob-watcher/node_modules/glob-parent/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-watcher/node_modules/is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "dependencies": { + "binary-extensions": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-watcher/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==", + "dev": true, + "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/glob-watcher/node_modules/readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/glob-watcher/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==", + "dev": true + }, + "node_modules/glob-watcher/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==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "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/global-dirs/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/global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "dependencies": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "dev": true, + "dependencies": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/globals": { + "version": "13.12.1", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz", + "integrity": "sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globals/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/globby": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.2.tgz", + "integrity": "sha512-2ZThXDvvV8fYFRVIxnrMQBipZQDr7MxKAmQK1vujaj9/7eF0efG7BPUKJ7jP7G5SLF37xKDXvO4S/KKLj/Z0og==", + "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/glogg": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", + "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", + "dev": true, + "dependencies": { + "sparkles": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "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/graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "node_modules/growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true, + "engines": { + "node": ">=4.x" + } + }, + "node_modules/gulp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", + "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", + "dev": true, + "dependencies": { + "glob-watcher": "^5.0.3", + "gulp-cli": "^2.2.0", + "undertaker": "^1.2.1", + "vinyl-fs": "^3.0.0" + }, + "bin": { + "gulp": "bin/gulp.js" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-typescript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/gulp-typescript/-/gulp-typescript-5.0.1.tgz", + "integrity": "sha512-YuMMlylyJtUSHG1/wuSVTrZp60k1dMEFKYOvDf7OvbAJWrDtxxD4oZon4ancdWwzjj30ztiidhe4VXJniF0pIQ==", + "dev": true, + "dependencies": { + "ansi-colors": "^3.0.5", + "plugin-error": "^1.0.1", + "source-map": "^0.7.3", + "through2": "^3.0.0", + "vinyl": "^2.1.0", + "vinyl-fs": "^3.0.3" + }, + "engines": { + "node": ">= 8" + }, + "peerDependencies": { + "typescript": "~2.7.1 || >=2.8.0-dev || >=2.9.0-dev || ~3.0.0 || >=3.0.0-dev || >=3.1.0-dev || >= 3.2.0-dev || >= 3.3.0-dev" + } + }, + "node_modules/gulp-typescript/node_modules/ansi-colors": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", + "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/gulp-typescript/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/gulp-typescript/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/gulp-typescript/node_modules/through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + }, + "node_modules/gulp/node_modules/ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "dev": true, + "dependencies": { + "ansi-wrap": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp/node_modules/camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp/node_modules/cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "dependencies": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, + "node_modules/gulp/node_modules/get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "node_modules/gulp/node_modules/gulp-cli": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.3.0.tgz", + "integrity": "sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A==", + "dev": true, + "dependencies": { + "ansi-colors": "^1.0.1", + "archy": "^1.0.0", + "array-sort": "^1.0.0", + "color-support": "^1.1.3", + "concat-stream": "^1.6.0", + "copy-props": "^2.0.1", + "fancy-log": "^1.3.2", + "gulplog": "^1.0.0", + "interpret": "^1.4.0", + "isobject": "^3.0.1", + "liftoff": "^3.1.0", + "matchdep": "^2.0.0", + "mute-stdout": "^1.0.0", + "pretty-hrtime": "^1.0.0", + "replace-homedir": "^1.0.0", + "semver-greatest-satisfied-range": "^1.1.0", + "v8flags": "^3.2.0", + "yargs": "^7.1.0" + }, + "bin": { + "gulp": "bin/gulp.js" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp/node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp/node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp/node_modules/require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "node_modules/gulp/node_modules/string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp/node_modules/which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "dev": true + }, + "node_modules/gulp/node_modules/wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "dependencies": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp/node_modules/y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true + }, + "node_modules/gulp/node_modules/yargs": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.1.tgz", + "integrity": "sha512-huO4Fr1f9PmiJJdll5kwoS2e4GqzGSsMT3PPMpOwoVkOK8ckqAewMTZyA6LXVQWflleb/Z8oPBEvNsMft0XE+g==", + "dev": true, + "dependencies": { + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "5.0.0-security.0" + } + }, + "node_modules/gulp/node_modules/yargs-parser": { + "version": "5.0.0-security.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0-security.0.tgz", + "integrity": "sha512-T69y4Ps64LNesYxeYGYPvfoMTt/7y1XtfpIslUeK4um+9Hu7hlGoRtaDLvdXb7+/tfq4opVa2HRY5xGip022rQ==", + "dev": true, + "dependencies": { + "camelcase": "^3.0.0", + "object.assign": "^4.1.0" + } + }, + "node_modules/gulplog": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", + "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", + "dev": true, + "dependencies": { + "glogg": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "dependencies": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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/hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "dev": true, + "dependencies": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "dependencies": { + "parse-passwd": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/hpagent": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-0.1.2.tgz", + "integrity": "sha512-ePqFXHtSQWAFXYmj+JtOTHr84iNrII4/QRlAAPPE+zqnKy4xJo7Ie1Y4kC7AdB+LxLxSTTzBMASsEcy0q8YyvQ==" + }, + "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==", + "dev": true + }, + "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/i": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/i/-/i-0.3.7.tgz", + "integrity": "sha512-FYz4wlXgkQwIPqhzC5TdNMLSE5+GS1IIDJZY/1ZiEPCT2S3COUVZeT5OW4BmW4r5LHLQuOosSwsvnroG9GR59Q==", + "engines": { + "node": ">=0.4" + } + }, + "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.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "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/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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflection": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", + "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=", + "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=", + "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": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "node_modules/interpret": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", + "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "dev": true, + "dependencies": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "node_modules/is-callable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", + "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/is-core-module": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", + "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-descriptor/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-dir/-/is-dir-1.0.0.tgz", + "integrity": "sha1-QdN/SV/MrMBaR3jWboMCTCkro/8=" + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "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-negated-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", + "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", + "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.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-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz", + "integrity": "sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "dev": true, + "dependencies": { + "is-unc-path": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "node_modules/is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "dev": true, + "dependencies": { + "unc-path-regex": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "node_modules/is-valid-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", + "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "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/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "dependencies": { + "append-transform": "^2.0.0" + }, + "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-processinfo": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz", + "integrity": "sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==", + "dev": true, + "dependencies": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.0", + "istanbul-lib-coverage": "^3.0.0-alpha.1", + "make-dir": "^3.0.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^3.3.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-processinfo/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/istanbul-lib-processinfo/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.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "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-report/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/istanbul-lib-report/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/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-lib-source-maps/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/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/jmespath": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", + "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=", + "engines": { + "node": ">= 0.6.0" + } + }, + "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==", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-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/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/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.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonpath": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.1.1.tgz", + "integrity": "sha512-l6Cg7jRpixfbgoWgkrl77dgEj8RPvND0wMH6TwQmi9Qs4TFfS9u5cUFnbeKTwj5ga5Y3BTGGNI28k117LJ009w==", + "dependencies": { + "esprima": "1.2.2", + "static-eval": "2.0.2", + "underscore": "1.12.1" + } + }, + "node_modules/jsonpath/node_modules/esprima": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.2.tgz", + "integrity": "sha1-dqD9Zvz+FU/SkmZ9wmQBl1CxZXs=", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=4", + "npm": ">=1.4.28" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/just-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.0.0.tgz", + "integrity": "sha1-h/zPrv/AtozRnVX2cilD+SnqNeo=", + "dev": true + }, + "node_modules/just-extend": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "dev": true + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jwks-rsa": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-2.0.5.tgz", + "integrity": "sha512-fliHfsiBRzEU0nXzSvwnh0hynzGB0WihF+CinKbSRlaqRxbqqKf2xbBPgwc8mzf18/WgwlG8e5eTpfSTBcU4DQ==", + "dependencies": { + "@types/express-jwt": "0.0.42", + "debug": "^4.3.2", + "jose": "^2.0.5", + "limiter": "^1.1.5", + "lru-memoizer": "^2.1.4" + }, + "engines": { + "node": ">=10 < 13 || >=14" + } + }, + "node_modules/jwks-rsa/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/jwks-rsa/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/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/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/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/knex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/knex/-/knex-1.0.1.tgz", + "integrity": "sha512-pusgMo74lEbUxmri+YfWV8x/LJacP/2KcemTCKH7WnXFYz5RoMi+8WM4OJ05b0glfF+aWB4nkFsxsXxJ8qioLQ==", + "dependencies": { + "colorette": "2.0.16", + "commander": "^8.3.0", + "debug": "4.3.3", + "escalade": "^3.1.1", + "esm": "^3.2.25", + "getopts": "2.3.0", + "interpret": "^2.2.0", + "lodash": "^4.17.21", + "pg-connection-string": "2.5.0", + "rechoir": "^0.8.0", + "resolve-from": "^5.0.0", + "tarn": "^3.0.2", + "tildify": "2.0.0" + }, + "bin": { + "knex": "bin/cli.js" + }, + "engines": { + "node": ">=12" + }, + "peerDependenciesMeta": { + "@vscode/sqlite3": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "mysql": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "pg": { + "optional": true + }, + "pg-native": { + "optional": true + }, + "sqlite3": { + "optional": true + }, + "tedious": { + "optional": true + } + } + }, + "node_modules/knex/node_modules/debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/knex/node_modules/is-core-module": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", + "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/knex/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/knex/node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/knex/node_modules/resolve": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "dependencies": { + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/knex/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + }, + "node_modules/last-run": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", + "integrity": "sha1-RblpQsF7HHnHchmCWbqUO+v4yls=", + "dev": true, + "dependencies": { + "default-resolution": "^2.0.0", + "es6-weak-map": "^2.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "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/lazystream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", + "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", + "dev": true, + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/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==", + "dev": true, + "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/lazystream/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==", + "dev": true + }, + "node_modules/lazystream/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==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dev": true, + "dependencies": { + "invert-kv": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lead": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", + "integrity": "sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=", + "dev": true, + "dependencies": { + "flush-write-stream": "^1.0.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "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/liftoff": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", + "integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==", + "dev": true, + "dependencies": { + "extend": "^3.0.0", + "findup-sync": "^3.0.0", + "fined": "^1.0.1", + "flagged-respawn": "^1.0.0", + "is-plain-object": "^2.0.4", + "object.map": "^1.0.0", + "rechoir": "^0.6.2", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" + }, + "node_modules/load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + }, + "engines": { + "node": ">=0.10.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==", + "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=" + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" + }, + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "dev": true + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "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.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "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==" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, + "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/log-symbols": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/logform": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.2.0.tgz", + "integrity": "sha512-N0qPlqfypFx7UHNn4B3lzS/b0uLqt2hmuoa+PpuXNYgozdJYAyauF5Ky0BWVjrxDlMWiT3qN4zPq3vVAfZy7Yg==", + "dependencies": { + "colors": "^1.2.1", + "fast-safe-stringify": "^2.0.4", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "triple-beam": "^1.3.0" + } + }, + "node_modules/logform/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/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/lru-cache": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", + "integrity": "sha1-HRdnnAac2l0ECZGgnbwsDbN35V4=", + "dependencies": { + "pseudomap": "^1.0.1", + "yallist": "^2.0.0" + } + }, + "node_modules/lru-memoizer": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.1.4.tgz", + "integrity": "sha512-IXAq50s4qwrOBrXJklY+KhgZF+5y98PDaNo0gi/v2KQBFLyWr+JyFvijZXkGKjQj/h9c0OwoE+JZbwUXce76hQ==", + "dependencies": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "~4.0.0" + } + }, + "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==", + "dev": true + }, + "node_modules/make-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", + "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "dependencies": { + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", + "integrity": "sha1-xvNINKDY28OzfCfui7yyfHd1WC4=", + "dev": true, + "dependencies": { + "findup-sync": "^2.0.0", + "micromatch": "^3.0.4", + "resolve": "^1.4.0", + "stack-trace": "0.0.10" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/matchdep/node_modules/findup-sync": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", + "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", + "dev": true, + "dependencies": { + "detect-file": "^1.0.0", + "is-glob": "^3.1.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/matchdep/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "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/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": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "dependencies": { + "mime-db": "1.44.0" + }, + "engines": { + "node": ">= 0.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==", + "dev": true, + "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==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" + }, + "node_modules/mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "dependencies": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mixin-deep/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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/mocha": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz", + "integrity": "sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==", + "dev": true, + "dependencies": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.1", + "debug": "4.3.1", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.1.6", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.0.0", + "log-symbols": "4.0.0", + "minimatch": "3.0.4", + "ms": "2.1.3", + "nanoid": "3.1.20", + "serialize-javascript": "5.0.1", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "wide-align": "1.1.3", + "workerpool": "6.1.0", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 10.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/mocha/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/mocha/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/mocha/node_modules/chokidar": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.1" + } + }, + "node_modules/mocha/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/mocha/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mocha/node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/mocha/node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/mocha/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/mocha/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/mocha/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/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/mocha/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/mocha/node_modules/js-yaml": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", + "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/mocha/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/mocha/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/mocha/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/mocha/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/mocha/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/mocha/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/mocha/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/mocha/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/mocha/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/mocha/node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/moment": { + "version": "2.29.2", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.2.tgz", + "integrity": "sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg==", + "engines": { + "node": "*" + } + }, + "node_modules/mongodb-uri": { + "version": "0.9.7", + "resolved": "https://registry.npmjs.org/mongodb-uri/-/mongodb-uri-0.9.7.tgz", + "integrity": "sha1-D3ca0W9IOuZfQoeWlCjp+8SqYYE=", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/multer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.3.tgz", + "integrity": "sha512-np0YLKncuZoTzufbkM6wEKp68EhWJXcU6fq6QqrSwkckd2LlMgd1UqhUJLj6NS/5sZ8dE8LYDWslsltJznnXlg==", + "deprecated": "Multer 1.x is affected by CVE-2022-24434. This is fixed in v1.4.4-lts.1 which drops support for versions of Node.js before 6. Please upgrade to at least Node.js 6 and version 1.4.4-lts.1 of Multer. If you need support for older versions of Node.js, we are open to accepting patches that would fix the CVE on the main 1.x release line, whilst maintaining compatibility with Node.js 0.10.", + "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/mute-stdout": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", + "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" + }, + "node_modules/nan": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", + "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", + "dev": true, + "optional": true + }, + "node_modules/nanoid": { + "version": "3.1.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", + "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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/ncp": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-1.0.1.tgz", + "integrity": "sha1-0VNn5cuHQyuhF9K/gP30Wuz7QkY=", + "bin": { + "ncp": "bin/ncp" + } + }, + "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/next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "dev": true + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node_modules/nise": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.0.tgz", + "integrity": "sha512-W5WlHu+wvo3PaKLsJJkgPup2LrsXCcm7AWwyNZkUnn5rwPkuPBi3Iwk5SQtN0mv+K65k7nKKjwNQ30wg3wLAQQ==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0", + "@sinonjs/fake-timers": "^7.0.4", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + } + }, + "node_modules/nise/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "node_modules/nise/node_modules/path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/node-fs": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/node-fs/-/node-fs-0.1.7.tgz", + "integrity": "sha1-MjI8zLRsn78PwRgS1FAhzDHTJbs=", + "os": [ + "linux", + "darwin", + "freebsd", + "win32", + "smartos", + "sunos" + ], + "engines": { + "node": ">=0.1.97" + } + }, + "node_modules/node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, + "dependencies": { + "process-on-spawn": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nodemon": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.14.tgz", + "integrity": "sha512-frcpDx+PviKEQRSYzwhckuO2zoHcBYLHI754RE9z5h1RGtrngerc04mLpQQCPWBkH/2ObrX7We9YiwVSYZpFJQ==", + "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/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/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-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "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": "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/now-and-later": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", + "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", + "dev": true, + "dependencies": { + "once": "^1.3.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/npm-run-all": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", + "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "memorystream": "^0.3.1", + "minimatch": "^3.0.4", + "pidtree": "^0.3.0", + "read-pkg": "^3.0.0", + "shell-quote": "^1.6.1", + "string.prototype.padend": "^3.0.0" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm-run-all/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/npm-run-all/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/npm-run-all/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/npm-run-all/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/npm-run-all/node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/npm-run-all/node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "dev": true, + "dependencies": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" + }, + "bin": { + "nyc": "bin/nyc.js" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/nyc/node_modules/p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/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/nyc/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/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-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "dependencies": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", + "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "dependencies": { + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.defaults": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", + "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", + "dev": true, + "dependencies": { + "array-each": "^1.0.1", + "array-slice": "^1.0.0", + "for-own": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", + "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", + "dev": true, + "dependencies": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.reduce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.reduce/-/object.reduce-1.0.1.tgz", + "integrity": "sha1-b+NI8qx/oPlcpiEiZZkJaCW7A60=", + "dev": true, + "dependencies": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.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/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/openapi-default-setter": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/openapi-default-setter/-/openapi-default-setter-9.3.0.tgz", + "integrity": "sha512-Y4PtlmeStp43dyy4x+ekibGrT/LYIz6Y9gnSJ0arELX/xc5uyTC7C2qJgeXf4RJcHW+yB9Q9QvyLUNDSa+8oFg==", + "dependencies": { + "openapi-types": "^9.3.0" + } + }, + "node_modules/openapi-framework": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/openapi-framework/-/openapi-framework-9.3.0.tgz", + "integrity": "sha512-mgeEqJcf18Fnd0MQ1I2T1fLljAtu6HkU0MknPM/IoVOXRDscKgQjzLIR/FyVfNcg358MXXsgUtVgDsbVQujyYA==", + "dependencies": { + "difunc": "0.0.4", + "fs-routes": "^9.0.3", + "glob": "*", + "is-dir": "^1.0.0", + "js-yaml": "^3.10.0", + "openapi-default-setter": "^9.3.0", + "openapi-request-coercer": "^9.3.0", + "openapi-request-validator": "^9.3.0", + "openapi-response-validator": "^9.3.0", + "openapi-schema-validator": "^9.3.0", + "openapi-security-handler": "^9.3.0", + "openapi-types": "^9.3.0", + "ts-log": "^2.1.4" + } + }, + "node_modules/openapi-jsonschema-parameters": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/openapi-jsonschema-parameters/-/openapi-jsonschema-parameters-9.3.0.tgz", + "integrity": "sha512-tUNAtzlJm5YaoqQMKvonRZN0BWRVRd34ulmGgzMLL+Ga23VnSy3FyFFI46LDUeIbh9wS2NGjkuO4akE01u7Rmw==", + "dependencies": { + "openapi-types": "^9.3.0" + } + }, + "node_modules/openapi-request-coercer": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/openapi-request-coercer/-/openapi-request-coercer-9.3.0.tgz", + "integrity": "sha512-5EvH0KeRZ3ygDljPTWFEXKvW9ga4h6HGiZN29H7F4g/OQBdKyFMCRpyUQZeVauJbuk6K5mvL6TdsmqdqI3D2Bg==", + "dependencies": { + "openapi-types": "^9.3.0", + "ts-log": "^2.1.4" + } + }, + "node_modules/openapi-request-validator": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/openapi-request-validator/-/openapi-request-validator-9.3.0.tgz", + "integrity": "sha512-SmpYM8HbCn6A22CS6ysvXItwWEpp/dJLqepCfh5F16S7Isy/7txbxGimM1xyhNZh+silXH8wjsac5jfbSniXgw==", + "dependencies": { + "ajv": "^8.3.0", + "ajv-formats": "^2.1.0", + "content-type": "^1.0.4", + "openapi-jsonschema-parameters": "^9.3.0", + "openapi-types": "^9.3.0", + "ts-log": "^2.1.4" + } + }, + "node_modules/openapi-response-validator": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/openapi-response-validator/-/openapi-response-validator-9.3.0.tgz", + "integrity": "sha512-pklr94TIvl/ObZ0Gs04ihYWSi6w4k7jAerw1rSBHklb/ZbFTS5iP1t753PdSW9/7QJdXzZP/9uMADkhyURNjwA==", + "dependencies": { + "ajv": "^8.4.0", + "openapi-types": "^9.3.0" + } + }, + "node_modules/openapi-schema-validator": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/openapi-schema-validator/-/openapi-schema-validator-9.3.0.tgz", + "integrity": "sha512-KlvgZMWTu+H1FHFSZNAGj369uXl3BD1nXSIq+sXlG6P+OrsAHd3YORx0ZEZ3WGdu2LQrPGmtowGQavYXL+PLwg==", + "dependencies": { + "ajv": "^8.1.0", + "ajv-formats": "^2.0.2", + "lodash.merge": "^4.6.1", + "openapi-types": "^9.3.0" + } + }, + "node_modules/openapi-security-handler": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/openapi-security-handler/-/openapi-security-handler-9.3.0.tgz", + "integrity": "sha512-loy+sdPxjb0OuzIj0cp45kowoLEQ8z6FF0QJBFxtfDttuDssTtQ3Vw5C2kAZ/6Qu6X1y6HT4DAYdDY3iJ3iMNw==", + "dependencies": { + "openapi-types": "^9.3.0" + } + }, + "node_modules/openapi-types": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-9.3.0.tgz", + "integrity": "sha512-sR23YjmuwDSMsQVZDHbV9mPgi0RyniQlqR0AQxTC2/F3cpSjRFMH3CFPjoWvNqhC4OxPkDYNb2l8Mc1Me6D/KQ==" + }, + "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/ordered-read-streams": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", + "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", + "dev": true, + "dependencies": { + "readable-stream": "^2.0.1" + } + }, + "node_modules/ordered-read-streams/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==", + "dev": true, + "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/ordered-read-streams/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==", + "dev": true + }, + "node_modules/ordered-read-streams/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==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "dev": true, + "dependencies": { + "lcid": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "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==", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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/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/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/parse-database-url": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/parse-database-url/-/parse-database-url-0.3.0.tgz", + "integrity": "sha1-NpZmMh6SfJreY838Gqr2+zdFPQ0=", + "dependencies": { + "mongodb-uri": ">= 0.9.7" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/parse-filepath": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", + "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", + "dev": true, + "dependencies": { + "is-absolute": "^1.0.0", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "dependencies": { + "error-ex": "^1.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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/pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "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==", + "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=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-root": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", + "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", + "dev": true, + "dependencies": { + "path-root-regex": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-root-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", + "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "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-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": "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/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/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.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", + "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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/pkginfo": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.4.1.tgz", + "integrity": "sha1-tUGO8EOd5UJfxJlQQtztFPsqhP8=", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/plugin-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", + "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", + "dev": true, + "dependencies": { + "ansi-colors": "^1.0.1", + "arr-diff": "^4.0.0", + "arr-union": "^3.1.0", + "extend-shallow": "^3.0.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/plugin-error/node_modules/ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "dev": true, + "dependencies": { + "ansi-wrap": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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": "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/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.2.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz", + "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==", + "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/prettier-plugin-organize-imports": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-2.3.4.tgz", + "integrity": "sha512-R8o23sf5iVL/U71h9SFUdhdOEPsi3nm42FD/oDYIZ2PQa4TNWWuWecxln6jlIQzpZTDMUeO1NicJP6lLn2TtRw==", + "dev": true, + "peerDependencies": { + "prettier": ">=2.0", + "typescript": ">=2.9" + } + }, + "node_modules/pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/printj": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", + "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==", + "bin": { + "printj": "bin/printj.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "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/process-on-spawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", + "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "dev": true, + "dependencies": { + "fromentries": "^1.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "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/prompt": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prompt/-/prompt-1.0.0.tgz", + "integrity": "sha1-jlcSPDlquYiJf7Mn/Trtw+c15P4=", + "dependencies": { + "colors": "^1.1.2", + "pkginfo": "0.x.x", + "read": "1.0.x", + "revalidator": "0.1.x", + "utile": "0.3.x", + "winston": "2.1.x" + }, + "engines": { + "node": ">= 0.6.6" + } + }, + "node_modules/prompt/node_modules/async": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz", + "integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=" + }, + "node_modules/prompt/node_modules/winston": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/winston/-/winston-2.1.1.tgz", + "integrity": "sha1-PJNJ0ZYgf9G9/51LxD73JRDjoS4=", + "dependencies": { + "async": "~1.0.0", + "colors": "1.0.x", + "cycle": "1.0.x", + "eyes": "0.1.x", + "isstream": "0.1.x", + "pkginfo": "0.3.x", + "stack-trace": "0.0.x" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prompt/node_modules/winston/node_modules/colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/prompt/node_modules/winston/node_modules/pkginfo": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.3.1.tgz", + "integrity": "sha1-Wyn2qB9wcXFC4J52W76rl7T4HiE=", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", + "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "dependencies": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + }, + "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": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "dependencies": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "node_modules/punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + }, + "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.10.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", + "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "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/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "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/read": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", + "dependencies": { + "mute-stream": "~0.0.4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "dependencies": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "dependencies": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg-up/node_modules/find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "dependencies": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg-up/node_modules/path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "dependencies": { + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg/node_modules/path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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/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/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "dependencies": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.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/release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "dev": true, + "dependencies": { + "es6-error": "^4.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/remove-bom-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", + "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5", + "is-utf8": "^0.2.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/remove-bom-stream": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", + "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=", + "dev": true, + "dependencies": { + "remove-bom-buffer": "^3.0.0", + "safe-buffer": "^5.1.0", + "through2": "^2.0.3" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "node_modules/repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/replace-ext": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", + "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/replace-homedir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-1.0.0.tgz", + "integrity": "sha1-6H9tUTuSjd6AgmDBK+f+xv9ueYw=", + "dev": true, + "dependencies": { + "homedir-polyfill": "^1.0.1", + "is-absolute": "^1.0.0", + "remove-trailing-separator": "^1.1.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "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=", + "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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, + "node_modules/resolve": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", + "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", + "dependencies": { + "is-core-module": "^2.1.0", + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "dev": true, + "dependencies": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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/resolve-options": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", + "integrity": "sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=", + "dev": true, + "dependencies": { + "value-or-function": "^3.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "deprecated": "https://github.com/lydell/resolve-url#deprecated", + "dev": true + }, + "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/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true, + "engines": { + "node": ">=0.12" + } + }, + "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/revalidator": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/revalidator/-/revalidator-0.1.8.tgz", + "integrity": "sha1-/s5hv6DBtSoga9axgZgYS91SOjs=", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/run-parallel": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.10.tgz", + "integrity": "sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw==", + "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/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "dependencies": { + "ret": "~0.1.10" + } + }, + "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/sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" + }, + "node_modules/secure-json-parse": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.4.0.tgz", + "integrity": "sha512-Q5Z/97nbON5t/L/sH6mY2EacfjVGwrCcSi5D3btRO2GZ8pf1K1UN7Z9H5J57hjVU2Qzxr1xO+FmBhOvEkzCMmg==" + }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/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/semver-greatest-satisfied-range": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz", + "integrity": "sha1-E+jCZYq5aRywzXEJMkAoDTb3els=", + "dev": true, + "dependencies": { + "sver-compat": "^1.5.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "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/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/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/serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "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/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "node_modules/set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/set-value/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.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/shell-quote": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz", + "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==", + "dev": true + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel/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==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "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/sinon": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-11.1.2.tgz", + "integrity": "sha512-59237HChms4kg7/sXhiRcUzdSkKuydDeTiamT/jesUVHshBgL8XAmhgFo0GfK6RruMDM/iRSij1EybmMog9cJw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.8.3", + "@sinonjs/fake-timers": "^7.1.2", + "@sinonjs/samsam": "^6.0.2", + "diff": "^5.0.0", + "nise": "^5.1.0", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/sinon-chai": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.7.0.tgz", + "integrity": "sha512-mf5NURdUaSdnatJx3uhoBOrY9dtL19fiOtAdT1Azxg3+lNJFiuN0uzaU3xX1LeAfL17kHQhTAJgpsfhbMJMY2g==", + "dev": true, + "peerDependencies": { + "chai": "^4.0.0", + "sinon": ">=4.0.0" + } + }, + "node_modules/sinon/node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/sinon/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/sinon/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/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/snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "dependencies": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "dependencies": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "dependencies": { + "kind-of": "^3.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/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/snapdragon/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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/source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", + "dev": true, + "dependencies": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/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-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "deprecated": "See https://github.com/lydell/source-map-url#deprecated", + "dev": true + }, + "node_modules/sparkles": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", + "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, + "dependencies": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/spawn-wrap/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/spawn-wrap/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/spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz", + "integrity": "sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ==", + "dev": true + }, + "node_modules/split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "dependencies": { + "extend-shallow": "^3.0.0" + }, + "engines": { + "node": ">=0.10.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/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "node_modules/sql-template-strings": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/sql-template-strings/-/sql-template-strings-2.2.2.tgz", + "integrity": "sha1-PxFQiiWt384hejBCqdMAwxk7lv8=", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/ssf": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", + "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", + "dependencies": { + "frac": "~1.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/ssh2": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-0.5.4.tgz", + "integrity": "sha1-G/a2soyW6u8mf01sRqWiUXpZnic=", + "dependencies": { + "ssh2-streams": "~0.1.15" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ssh2-streams": { + "version": "0.1.20", + "resolved": "https://registry.npmjs.org/ssh2-streams/-/ssh2-streams-0.1.20.tgz", + "integrity": "sha1-URGNFUVV31Rp7h9n4M8efoosDjo=", + "dependencies": { + "asn1": "~0.2.0", + "semver": "^5.1.0", + "streamsearch": "~0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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/static-eval": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.2.tgz", + "integrity": "sha512-N/D219Hcr2bPjLxPiV+TQE++Tsmrady7TqAJugLy7Xk1EumfDWS/f5dtBbkRCGE7wKKXuYockQoj8Rm2/pVKyg==", + "dependencies": { + "escodegen": "^1.8.1" + } + }, + "node_modules/static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "dependencies": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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/stream-exhaust": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", + "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", + "dev": true + }, + "node_modules/stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "dev": true + }, + "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": "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/string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.padend": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.1.tgz", + "integrity": "sha512-eCzTASPnoCr5Ht+Vn1YXgm8SB015hHKgEIMu9Nr9bQmLhRBxKRfmzSj/IQsxDFc8JInJDDFA0qXwK+xxI7wDkg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz", + "integrity": "sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz", + "integrity": "sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "dependencies": { + "is-utf8": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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=", + "engines": { + "node": ">=0.10.0" + } + }, + "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/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sver-compat": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/sver-compat/-/sver-compat-1.5.0.tgz", + "integrity": "sha1-PPh9/rTQe0o/FIJ7wYaz/QxkXNg=", + "dev": true, + "dependencies": { + "es6-iterator": "^2.0.1", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/swagger-ui-dist": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.3.0.tgz", + "integrity": "sha512-RY1c3y6uuHBTu4nZPXcvrv9cnKj6MbaNMZK1NDyGHrUbQOO5WmkuMo6wi93WFzSURJk0SboD1X9nM5CtQAu2Og==" + }, + "node_modules/swagger-ui-express": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.3.0.tgz", + "integrity": "sha512-jN46SEEe9EoXa3ZgZoKgnSF6z0w3tnM1yqhO4Y+Q4iZVc8JOQB960EZpIAz6rNROrDApVDwcMHR0mhlnc/5Omw==", + "dependencies": { + "swagger-ui-dist": ">=4.1.3" + }, + "engines": { + "node": ">= v0.10.32" + }, + "peerDependencies": { + "express": ">=4.0.0" + } + }, + "node_modules/table": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", + "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.1", + "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/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/table/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/table/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/tarn": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.2.tgz", + "integrity": "sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ==", + "engines": { + "node": ">=8.0.0" + } + }, + "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/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/through2-filter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", + "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", + "dev": true, + "dependencies": { + "through2": "~2.0.0", + "xtend": "~4.0.0" + } + }, + "node_modules/through2/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==", + "dev": true, + "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/through2/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==", + "dev": true + }, + "node_modules/through2/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==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/tildify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz", + "integrity": "sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/time-stamp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", + "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-absolute-glob": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", + "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", + "dev": true, + "dependencies": { + "is-absolute": "^1.0.0", + "is-negated-glob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-object-path/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "dependencies": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-through": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", + "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=", + "dev": true, + "dependencies": { + "through2": "^2.0.3" + }, + "engines": { + "node": ">= 0.10" + } + }, + "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/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/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-log": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/ts-log/-/ts-log-2.2.3.tgz", + "integrity": "sha512-XvB+OdKSJ708Dmf9ore4Uf/q62AYDTzFcAdxc8KNML1mmAWywRFVt/dn1KYJH8Agt5UJNujfM3znU5PxgAzA2w==" + }, + "node_modules/ts-mocha": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ts-mocha/-/ts-mocha-8.0.0.tgz", + "integrity": "sha512-Kou1yxTlubLnD5C3unlCVO7nh0HERTezjoVhVw/M5S1SqoUec0WgllQvPk3vzPMc6by8m6xD1uR1yRf8lnVUbA==", + "dev": true, + "dependencies": { + "ts-node": "7.0.1" + }, + "bin": { + "ts-mocha": "bin/ts-mocha" + }, + "engines": { + "node": ">= 6.X.X" + }, + "optionalDependencies": { + "tsconfig-paths": "^3.5.0" + }, + "peerDependencies": { + "mocha": "^3.X.X || ^4.X.X || ^5.X.X || ^6.X.X || ^7.X.X || ^8.X.X" + } + }, + "node_modules/ts-mocha/node_modules/diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/ts-mocha/node_modules/ts-node": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz", + "integrity": "sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==", + "dev": true, + "dependencies": { + "arrify": "^1.0.0", + "buffer-from": "^1.1.0", + "diff": "^3.1.0", + "make-error": "^1.1.1", + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "source-map-support": "^0.5.6", + "yn": "^2.0.0" + }, + "bin": { + "ts-node": "dist/bin.js" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/ts-node": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.4.0.tgz", + "integrity": "sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "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" + }, + "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.6.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.6.0.tgz", + "integrity": "sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ts-node/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" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", + "integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==", + "dev": true, + "optional": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "optional": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true, + "optional": true, + "engines": { + "node": ">=4" + } + }, + "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/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/tunnel-ssh": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tunnel-ssh/-/tunnel-ssh-4.1.4.tgz", + "integrity": "sha512-CjBqboGvAbM7iXSX2F95kzoI+c2J81YkrHbyyo4SWNKCzU6w5LfEvXBCHu6PPriYaNvfhMKzD8bFf5Vl14YTtg==", + "dependencies": { + "debug": "2.6.9", + "lodash.defaults": "^4.1.0", + "ssh2": "0.5.4" + } + }, + "node_modules/tunnel-ssh/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/type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", + "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.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "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.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.6.tgz", + "integrity": "sha512-pxnwLxeb/Z5SP80JDRzVjh58KsM6jZHRAOtTpS7sXLS4ogXNKC9ANxHHZqLLeVHZN35jCtI4JdmLLbLiC1kBow==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, + "node_modules/underscore": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", + "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==" + }, + "node_modules/undertaker": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.3.0.tgz", + "integrity": "sha512-/RXwi5m/Mu3H6IHQGww3GNt1PNXlbeCuclF2QYR14L/2CHPz3DFZkvB5hZ0N/QUkiXWCACML2jXViIQEQc2MLg==", + "dev": true, + "dependencies": { + "arr-flatten": "^1.0.1", + "arr-map": "^2.0.0", + "bach": "^1.0.0", + "collection-map": "^1.0.0", + "es6-weak-map": "^2.0.1", + "fast-levenshtein": "^1.0.0", + "last-run": "^1.1.0", + "object.defaults": "^1.0.0", + "object.reduce": "^1.0.0", + "undertaker-registry": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/undertaker-registry": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-1.0.1.tgz", + "integrity": "sha1-XkvaMI5KiirlhPm5pDWaSZglzFA=", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/undertaker/node_modules/fast-levenshtein": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.1.4.tgz", + "integrity": "sha1-5qdUzI8V5YmHqpy9J69m/W9OWvk=", + "dev": true + }, + "node_modules/undici": { + "version": "4.16.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-4.16.0.tgz", + "integrity": "sha512-tkZSECUYi+/T1i4u+4+lwZmQgLXd4BLGlrc7KZPcLIW7Jpq99+Xpc30ONv7nS6F5UNOxp/HBZSSL9MafUrvJbw==", + "engines": { + "node": ">=12.18" + } + }, + "node_modules/union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "dependencies": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unique-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", + "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", + "dev": true, + "dependencies": { + "json-stable-stringify-without-jsonify": "^1.0.1", + "through2-filter": "^3.0.0" + } + }, + "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/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/unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "dependencies": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "dependencies": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "dependencies": { + "isarray": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true, + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "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/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/update-notifier/node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/update-notifier/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uri-js/node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "deprecated": "Please see https://github.com/lydell/urix#deprecated", + "dev": true + }, + "node_modules/url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "dependencies": { + "punycode": "1.3.2", + "querystring": "0.2.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/use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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/utile": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/utile/-/utile-0.3.0.tgz", + "integrity": "sha1-E1LDQOuCDk2N26A5pPv6oy7U7zo=", + "dependencies": { + "async": "~0.9.0", + "deep-equal": "~0.2.1", + "i": "0.3.x", + "mkdirp": "0.x.x", + "ncp": "1.0.x", + "rimraf": "2.x.x" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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/v8flags": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", + "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", + "dev": true, + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/value-or-function": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", + "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=", + "dev": true, + "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/vinyl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", + "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", + "dev": true, + "dependencies": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl-fs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", + "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", + "dev": true, + "dependencies": { + "fs-mkdirp-stream": "^1.0.0", + "glob-stream": "^6.1.0", + "graceful-fs": "^4.0.0", + "is-valid-glob": "^1.0.0", + "lazystream": "^1.0.0", + "lead": "^1.0.0", + "object.assign": "^4.0.4", + "pumpify": "^1.3.5", + "readable-stream": "^2.3.3", + "remove-bom-buffer": "^3.0.0", + "remove-bom-stream": "^1.2.0", + "resolve-options": "^1.1.0", + "through2": "^2.0.0", + "to-through": "^2.0.0", + "value-or-function": "^3.0.0", + "vinyl": "^2.0.0", + "vinyl-sourcemap": "^1.1.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl-fs/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==", + "dev": true, + "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/vinyl-fs/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==", + "dev": true + }, + "node_modules/vinyl-fs/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==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/vinyl-sourcemap": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", + "integrity": "sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY=", + "dev": true, + "dependencies": { + "append-buffer": "^1.0.2", + "convert-source-map": "^1.5.0", + "graceful-fs": "^4.1.6", + "normalize-path": "^2.1.1", + "now-and-later": "^2.0.0", + "remove-bom-buffer": "^3.0.0", + "vinyl": "^2.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl-sourcemap/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/when": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/when/-/when-2.0.1.tgz", + "integrity": "sha1-jYcv4V5oQkyRtLck6EjggH2rZkI=" + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + }, + "node_modules/wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "dependencies": { + "string-width": "^1.0.2 || 2" + } + }, + "node_modules/wide-align/node_modules/ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/wide-align/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/wide-align/node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/wide-align/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "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-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/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/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/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/async": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", + "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==" + }, + "node_modules/wmf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", + "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/word": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", + "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", + "engines": { + "node": ">=0.8" + } + }, + "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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/workerpool": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", + "integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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/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/xlsx": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.17.3.tgz", + "integrity": "sha512-dGZKfyPSXfnoITruwisuDVZkvnxhjgqzWJXBJm2Khmh01wcw8//baRUvhroVRhW2SLbnlpGcCZZbeZO1qJgMIw==", + "dependencies": { + "adler-32": "~1.2.0", + "cfb": "^1.1.4", + "codepage": "~1.15.0", + "commander": "~2.17.1", + "crc-32": "~1.2.0", + "exit-on-epipe": "~1.0.1", + "fflate": "^0.7.1", + "ssf": "~0.11.2", + "wmf": "~1.0.1", + "word": "~0.3.0" + }, + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/xlsx/node_modules/commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==" + }, + "node_modules/xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "engines": { + "node": ">=4.0" + } + }, + "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": "4.0.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", + "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==" + }, + "node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + }, + "node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser/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/yargs-unparser/node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", + "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, "dependencies": { "@babel/code-frame": { "version": "7.10.4", @@ -1220,7 +13755,8 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true + "dev": true, + "requires": {} }, "acorn-walk": { "version": "8.2.0", @@ -1553,7 +14089,7 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "atob": { "version": "2.1.2", @@ -4132,7 +16668,8 @@ "fs-routes": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/fs-routes/-/fs-routes-9.0.3.tgz", - "integrity": "sha512-Y5tkylY9fQ1jm11FdJoptzqIG3OyzqrOF16W5odNlIdqFqb2355IbNB3jQkE+C268mSShLmIur8ynYCgL/Yg/g==" + "integrity": "sha512-Y5tkylY9fQ1jm11FdJoptzqIG3OyzqrOF16W5odNlIdqFqb2355IbNB3jQkE+C268mSShLmIur8ynYCgL/Yg/g==", + "requires": {} }, "fs.realpath": { "version": "1.0.0", @@ -7379,7 +19916,8 @@ "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==" + "integrity": "sha512-TVHxR/gf3MeJRvchgNHxsYsTCHQ+4wm3VIHSS19z8NC0+gioEhq1okDY1sm/TYbfoP6JLFx01s0ShvZ3puP/iQ==", + "requires": {} }, "pg-protocol": { "version": "1.5.0", @@ -7536,7 +20074,8 @@ "version": "2.3.4", "resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-2.3.4.tgz", "integrity": "sha512-R8o23sf5iVL/U71h9SFUdhdOEPsi3nm42FD/oDYIZ2PQa4TNWWuWecxln6jlIQzpZTDMUeO1NicJP6lLn2TtRw==", - "dev": true + "dev": true, + "requires": {} }, "pretty-hrtime": { "version": "1.0.3", @@ -8262,7 +20801,8 @@ "version": "3.7.0", "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.7.0.tgz", "integrity": "sha512-mf5NURdUaSdnatJx3uhoBOrY9dtL19fiOtAdT1Azxg3+lNJFiuN0uzaU3xX1LeAfL17kHQhTAJgpsfhbMJMY2g==", - "dev": true + "dev": true, + "requires": {} }, "slash": { "version": "3.0.0", @@ -8621,6 +21161,14 @@ "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" }, + "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" + } + }, "string-width": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", @@ -8662,14 +21210,6 @@ "define-properties": "^1.1.3" } }, - "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" - } - }, "strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", diff --git a/api/src/app.ts b/api/src/app.ts index 36088a5f35..122d02e1ca 100644 --- a/api/src/app.ts +++ b/api/src/app.ts @@ -156,6 +156,13 @@ function validateAllResponses(req: Request, res: Response, next: NextFunction) { if (!isStrictValidation || !validationResult?.errors) { return json.apply(res, args); } else { + defaultLog.debug({ + label: 'validateAllResponses', + message: validationMessage, + responseBody: body, + errors: errorList + }); + return res.status(500).json({ name: HTTPErrorType.INTERNAL_SERVER_ERROR, status: 500, diff --git a/api/src/constants/codes.ts b/api/src/constants/codes.ts index b48e19332d..c0d5b5ede9 100644 --- a/api/src/constants/codes.ts +++ b/api/src/constants/codes.ts @@ -1,3 +1,5 @@ +// Note, more recent additions to this list may not be in order based on their `id`. If adding a new item to this list, +// double check what the highest id is (don't assume its based on the item at the bottom of this list). export const coordinator_agency = [ { id: 1, name: 'A Rocha Canada' }, { id: 2, name: 'Aarde Environmental Ltd.' }, @@ -153,7 +155,9 @@ export const coordinator_agency = [ { id: 152, name: 'Michigan State University' }, { id: 153, name: 'Mid Vancouver Island Habitat Enhancement Society' }, { id: 154, name: 'Ministry of Environment & Climate Change Strategy' }, + { id: 267, name: 'Ministry of Forests' }, { id: 155, name: 'Ministry of Forests, Lands, Natural Resource Operations & Rural Development' }, + { id: 266, name: 'Ministry of Lands, Water, and Resource Stewardship' }, { id: 156, name: 'Ministry of Transportation & Infrastructure' }, { id: 157, name: 'Minnow Environmental Inc.' }, { id: 158, name: 'Montana Fish, Wildlife & Parks' }, diff --git a/api/src/constants/database.ts b/api/src/constants/database.ts index e07bdd2a8a..4b0b1f47d7 100644 --- a/api/src/constants/database.ts +++ b/api/src/constants/database.ts @@ -9,3 +9,8 @@ export enum SYSTEM_IDENTITY_SOURCE { IDIR = 'IDIR', BCEID = 'BCEID' } + +export enum SCHEMAS { + API = 'BIOHUB_DAPI_V1', + DATA = 'BIOHUB' +} diff --git a/api/src/constants/status.ts b/api/src/constants/status.ts index 49cfc4d3a8..249055dae8 100644 --- a/api/src/constants/status.ts +++ b/api/src/constants/status.ts @@ -25,7 +25,6 @@ export enum SUBMISSION_STATUS_TYPE { 'SUBMISSION_DATA_INGESTED' = 'Submission Data Ingested', 'SECURED' = 'Secured', 'AWAITING CURRATION' = 'Awaiting Curration', - 'PUBLISHED' = 'Published', 'REJECTED' = 'Rejected', 'ON HOLD' = 'On Hold', 'SYSTEM_ERROR' = 'System Error' diff --git a/api/src/models/occurrence-create.ts b/api/src/models/occurrence-create.ts index f37cd76c5c..b0b72e86ad 100644 --- a/api/src/models/occurrence-create.ts +++ b/api/src/models/occurrence-create.ts @@ -1,7 +1,3 @@ -import { getLogger } from '../utils/logger'; - -const defaultLog = getLogger('models/occurrence-create'); - /** * Pre-processes POST occurrences data * @@ -21,8 +17,6 @@ export class PostOccurrence { eventDate: string; constructor(obj?: any) { - defaultLog.debug({ label: 'PostOccurrence', message: 'params', obj }); - this.associatedTaxa = obj?.associatedTaxa || null; this.lifeStage = obj?.lifeStage || null; this.sex = obj?.sex || null; diff --git a/api/src/models/occurrence-view.ts b/api/src/models/occurrence-view.ts index f19c486070..df8655a636 100644 --- a/api/src/models/occurrence-view.ts +++ b/api/src/models/occurrence-view.ts @@ -1,7 +1,3 @@ -import { getLogger } from '../utils/logger'; - -const defaultLog = getLogger('models/occurrence-view'); - /** * Pre-processes GET occurrences data for view-only purposes * @@ -12,17 +8,6 @@ export class GetOccurrencesViewData { occurrences: any[]; constructor(occurrencesData?: any) { - defaultLog.debug({ - label: 'GetOccurrencesViewData', - message: 'params', - occurrencesData: { - ...occurrencesData, - geometry: occurrencesData?.geometry?.map((item: any) => { - return { ...item, geometry: 'Too big to print' }; - }) - } - }); - this.occurrences = occurrencesData?.map((occurrence: any) => { const feature = (occurrence.geometry && { type: 'Feature', geometry: JSON.parse(occurrence.geometry), properties: {} }) || null; diff --git a/api/src/models/permit-no-sampling.test.ts b/api/src/models/permit-no-sampling.test.ts deleted file mode 100644 index 5c2f0ecfca..0000000000 --- a/api/src/models/permit-no-sampling.test.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { expect } from 'chai'; -import { describe } from 'mocha'; -import { PostPermitNoSamplingObject } from './permit-no-sampling'; -import { PostPermitData } from './project-create'; - -describe('postPermitNoSamplingObject', () => { - describe('No values provided', () => { - let postPermitNoSamplingObject: PostPermitNoSamplingObject; - - before(() => { - postPermitNoSamplingObject = new PostPermitNoSamplingObject(null); - }); - - it('sets coordinator to default values', function () { - expect(postPermitNoSamplingObject.coordinator).to.equal(null); - }); - - it('sets permit to default values', function () { - expect(postPermitNoSamplingObject.permit).to.equal(null); - }); - }); - - describe('All values provided', () => { - let postPermitNoSamplingObject: PostPermitNoSamplingObject; - - const obj = { - coordinator: { - first_name: 'first_name', - last_name: 'last_name', - email_address: 'email_address', - coordinator_agency: 'coordinator_agency', - share_contact_details: 'true' - }, - permit: { - permits: [ - { - permit_number: '123', - permit_type: 'type 1' - }, - { - permit_number: '456', - permit_type: 'type 2' - } - ], - existing_permits: [1, 2] - } - }; - - before(() => { - postPermitNoSamplingObject = new PostPermitNoSamplingObject(obj); - }); - - it('sets coordinator', function () { - expect(postPermitNoSamplingObject.coordinator).to.deep.equal({ - first_name: 'first_name', - last_name: 'last_name', - email_address: 'email_address', - coordinator_agency: 'coordinator_agency', - share_contact_details: true - }); - }); - - it('sets permit', function () { - expect(postPermitNoSamplingObject.permit).to.deep.equal({ - permits: [ - { - permit_number: '123', - permit_type: 'type 1' - }, - { - permit_number: '456', - permit_type: 'type 2' - } - ], - existing_permits: [ - { - permit_id: 1 - }, - { - permit_id: 2 - } - ] - }); - }); - }); -}); - -describe('PostPermitNoSamplingData', () => { - describe('No values provided', () => { - let postPermitNoSamplingData: PostPermitData; - - before(() => { - postPermitNoSamplingData = new PostPermitData(null); - }); - - it('sets permit to default values', function () { - expect(postPermitNoSamplingData.permits).to.eql([]); - }); - }); - - describe('All values provided where permits has no length', () => { - let postPermitNoSamplingData: PostPermitData; - - const obj = { permits: [] }; - - before(() => { - postPermitNoSamplingData = new PostPermitData(obj); - }); - - it('sets permits', function () { - expect(postPermitNoSamplingData.permits).to.eql([]); - }); - }); - - describe('All values provided where permits is null', () => { - let postPermitNoSamplingData: PostPermitData; - - const obj = { permits: null }; - - before(() => { - postPermitNoSamplingData = new PostPermitData(obj); - }); - - it('sets permits', function () { - expect(postPermitNoSamplingData.permits).to.eql([]); - }); - }); - - describe('All values provided where permits is a valid array', () => { - let postPermitNoSamplingData: PostPermitData; - - const obj = { permits: [{ permit_number: 1, permit_type: 'type' }] }; - - before(() => { - postPermitNoSamplingData = new PostPermitData(obj); - }); - - it('sets permits', function () { - expect(postPermitNoSamplingData.permits).to.eql([ - { - permit_number: 1, - permit_type: 'type' - } - ]); - }); - }); -}); diff --git a/api/src/models/permit-no-sampling.ts b/api/src/models/permit-no-sampling.ts deleted file mode 100644 index 18da6fc329..0000000000 --- a/api/src/models/permit-no-sampling.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { getLogger } from '../utils/logger'; -import { PostCoordinatorData, PostPermitData } from './project-create'; - -const defaultLog = getLogger('models/permit-no-sampling'); - -/** - * Processes POST /permit-no-sampling request data when no sampling is conducted. - * - * @export - * @class PostPermitNoSamplingObject - */ -export class PostPermitNoSamplingObject { - coordinator: PostCoordinatorData; - permit: PostPermitData; - - constructor(obj?: any) { - defaultLog.debug({ label: 'PostPermitNoSamplingObject', message: 'params', obj }); - - this.coordinator = (obj?.coordinator && new PostCoordinatorData(obj.coordinator)) || null; - this.permit = (obj?.permit && new PostPermitData(obj.permit)) || null; - } -} - -export interface IPostPermitNoSampling { - permit_number: string; - permit_type: string; -} diff --git a/api/src/models/project-create.test.ts b/api/src/models/project-create.test.ts index 1925ad1ece..202c056bff 100644 --- a/api/src/models/project-create.test.ts +++ b/api/src/models/project-create.test.ts @@ -8,7 +8,6 @@ import { PostLocationData, PostObjectivesData, PostPartnershipsData, - PostPermitData, PostProjectData, PostProjectObject } from './project-create'; @@ -25,10 +24,6 @@ describe('PostProjectObject', () => { expect(projectPostObject.coordinator).to.equal(null); }); - it('sets permit', function () { - expect(projectPostObject.permit).to.equal(null); - }); - it('sets project', function () { expect(projectPostObject.project).to.equal(null); }); @@ -65,13 +60,6 @@ describe('PostProjectObject', () => { coordinator_agency: 'agency', share_contact_details: 'true' }, - permit: { - permits: [ - { - permit_number: 1 - } - ] - }, project: { project_name: 'name_test_data', project_type: 'test_type', @@ -255,100 +243,6 @@ describe('PostObjectivesData', () => { }); }); -describe('PostPermitData', () => { - describe('No values provided', () => { - let projectPermitData: PostPermitData; - - before(() => { - projectPermitData = new PostPermitData(null); - }); - - it('sets permits', function () { - expect(projectPermitData.permits).to.eql([]); - }); - }); - - describe('All values provided are null', () => { - let projectPermitData: PostPermitData; - - before(() => { - projectPermitData = new PostPermitData({ - permits: null - }); - }); - - it('sets permits', function () { - expect(projectPermitData.permits).to.eql([]); - }); - }); - - describe('All values provided are empty arrays', () => { - let projectPermitData: PostPermitData; - - before(() => { - projectPermitData = new PostPermitData({ - permits: [] - }); - }); - - it('sets permits', function () { - expect(projectPermitData.permits).to.eql([]); - }); - }); - - describe('All values provided with sampling conducted as true', () => { - let projectPermitData: PostPermitData; - - const obj = { - permits: [ - { - permit_number: '1', - permit_type: 'permit type' - } - ] - }; - - before(() => { - projectPermitData = new PostPermitData(obj); - }); - - it('sets permits', function () { - expect(projectPermitData.permits).to.eql([ - { - permit_number: '1', - permit_type: 'permit type' - } - ]); - }); - }); - - describe('All values provided with sampling conducted as false', () => { - let projectPermitData: PostPermitData; - - const obj = { - permits: [ - { - permit_number: '1', - permit_type: 'permit type' - } - ] - }; - - before(() => { - projectPermitData = new PostPermitData(obj); - }); - - it('sets permits', function () { - expect(projectPermitData.permits).to.eql([ - { - permit_number: '1', - permit_type: 'permit type' - } - ]); - }); - }); -}); - describe('PostCoordinatorData', () => { describe('No values provided', () => { let projectCoordinatorData: PostCoordinatorData; diff --git a/api/src/models/project-create.ts b/api/src/models/project-create.ts index 2bf8bdc510..872827b96c 100644 --- a/api/src/models/project-create.ts +++ b/api/src/models/project-create.ts @@ -11,7 +11,6 @@ const defaultLog = getLogger('models/project-create'); */ export class PostProjectObject { coordinator: PostCoordinatorData; - permit: PostPermitData; project: PostProjectData; objectives: PostObjectivesData; location: PostLocationData; @@ -23,7 +22,6 @@ export class PostProjectObject { defaultLog.debug({ label: 'PostProjectObject', message: 'params', obj }); this.coordinator = (obj?.coordinator && new PostCoordinatorData(obj.coordinator)) || null; - this.permit = (obj?.permit && new PostPermitData(obj.permit)) || null; this.project = (obj?.project && new PostProjectData(obj.project)) || null; this.objectives = (obj?.project && new PostObjectivesData(obj.objectives)) || null; this.location = (obj?.location && new PostLocationData(obj.location)) || null; @@ -57,49 +55,6 @@ export class PostCoordinatorData { } } -export interface IPostPermit { - permit_number: string; - permit_type: string; -} - -export interface IPostExistingPermit { - permit_id: number; -} - -/** - * Processes POST /project permit data - * - * @export - * @class PostPermitData - */ -export class PostPermitData { - permits: IPostPermit[]; - existing_permits: IPostExistingPermit[]; - - constructor(obj?: any) { - defaultLog.debug({ label: 'PostPermitData', message: 'params', obj }); - - this.permits = - (obj?.permits?.length && - obj.permits.map((item: any) => { - return { - permit_number: item.permit_number, - permit_type: item.permit_type - }; - })) || - []; - - this.existing_permits = - (obj?.existing_permits?.length && - obj.existing_permits.map((item: any) => { - return { - permit_id: item - }; - })) || - []; - } -} - /** * Processes POST /project project data. * diff --git a/api/src/models/project-view.test.ts b/api/src/models/project-view.test.ts index bf68d2862d..ec6d4c1207 100644 --- a/api/src/models/project-view.test.ts +++ b/api/src/models/project-view.test.ts @@ -2,14 +2,15 @@ import { expect } from 'chai'; import { describe } from 'mocha'; import { COMPLETION_STATUS } from '../constants/status'; import { + GetAttachmentsData, GetCoordinatorData, GetFundingData, GetIUCNClassificationData, GetLocationData, GetObjectivesData, GetPartnershipsData, - GetPermitData, - GetProjectData + GetProjectData, + GetReportAttachmentsData } from './project-view'; describe('GetProjectData', () => { @@ -110,7 +111,8 @@ describe('GetObjectivesData', () => { const obj = { objectives: 'these are the project objectives', - caveats: 'these are some interesting caveats' + caveats: 'these are some interesting caveats', + revision_count: 'revision' }; before(() => { @@ -124,6 +126,10 @@ describe('GetObjectivesData', () => { it('sets caveats', function () { expect(projectObjectivesData.caveats).to.equal(obj.caveats); }); + + it('sets revision_count', function () { + expect(projectObjectivesData.revision_count).to.equal(obj.revision_count); + }); }); }); @@ -164,7 +170,8 @@ describe('GetCoordinatorData', () => { coordinator_last_name: 'last', coordinator_email_address: 'email@example.com', coordinator_agency_name: 'agency', - coordinator_public: true + coordinator_public: true, + revision_count: 'count' }; before(() => { @@ -190,43 +197,9 @@ describe('GetCoordinatorData', () => { it('sets share_contact_details', function () { expect(projectCoordinatorData.share_contact_details).to.equal('true'); }); - }); -}); - -describe('GetPermitData', () => { - describe('No values provided', () => { - let projectPermitData: GetPermitData; - - before(() => { - projectPermitData = new GetPermitData((null as unknown) as any[]); - }); - it('sets permits', function () { - expect(projectPermitData.permits).to.eql([]); - }); - }); - - describe('All values provided', () => { - let projectPermitData: GetPermitData; - - const permits = [ - { - number: '1', - type: 'permit type' - } - ]; - - before(() => { - projectPermitData = new GetPermitData(permits); - }); - - it('sets permits', function () { - expect(projectPermitData.permits).to.eql([ - { - permit_number: '1', - permit_type: 'permit type' - } - ]); + it('sets revision_count', function () { + expect(projectCoordinatorData.revision_count).to.equal('count'); }); }); }); @@ -284,11 +257,13 @@ describe('GetLocationData', () => { const locationDataObj = [ { location_description, - geometry + geometry, + revision_count: 'count' }, { location_description, - geometry + geometry, + revision_count: 'count' } ]; @@ -303,6 +278,10 @@ describe('GetLocationData', () => { it('sets the geometry', function () { expect(locationData.geometry).to.eql(geometry); }); + + it('sets revision_count', function () { + expect(locationData.revision_count).to.equal('count'); + }); }); }); @@ -366,7 +345,7 @@ describe('GetFundingData', () => { projectFundingData = new GetFundingData((null as unknown) as any[]); }); - it('sets permits', function () { + it('sets funding sources', function () { expect(projectFundingData.fundingSources).to.eql([]); }); }); @@ -378,7 +357,7 @@ describe('GetFundingData', () => { projectFundingData = new GetFundingData([]); }); - it('sets classification details', function () { + it('sets funding sources', function () { expect(projectFundingData.fundingSources).to.eql([]); }); }); @@ -405,7 +384,7 @@ describe('GetFundingData', () => { projectFundingData = new GetFundingData(fundings); }); - it('sets permits', function () { + it('sets funding sources', function () { expect(projectFundingData.fundingSources).to.eql(fundings); }); }); @@ -501,3 +480,212 @@ describe('GetPartnershipsData', () => { }); }); }); + +describe('GetAttachmentsData', () => { + describe('No values provided', () => { + let data: GetAttachmentsData; + + before(() => { + data = new GetAttachmentsData((null as unknown) as any[]); + }); + + it('sets attachmentDetails', function () { + expect(data.attachmentDetails).to.eql([]); + }); + }); + + describe('Empty arrays as values provided', () => { + let data: GetAttachmentsData; + + before(() => { + data = new GetAttachmentsData([]); + }); + + it('sets attachmentDetails', function () { + expect(data.attachmentDetails).to.eql([]); + }); + }); + + describe('some attachmentDetails values provided', () => { + let data: GetAttachmentsData; + + const attachmentDetails = [{ file_name: 1 }, { file_name: 2 }]; + + before(() => { + data = new GetAttachmentsData(attachmentDetails); + }); + + it('sets file_name', function () { + expect(data.attachmentDetails).to.eql([ + { + file_name: 1, + file_type: undefined, + title: undefined, + description: undefined, + key: undefined, + file_size: undefined, + is_secure: 'false' + }, + { + file_name: 2, + file_type: undefined, + title: undefined, + description: undefined, + key: undefined, + file_size: undefined, + is_secure: 'false' + } + ]); + }); + }); + + describe('all attachmentDetails values provided', () => { + let data: GetAttachmentsData; + + const attachmentDetails = [ + { + file_name: 1, + file_type: 'type', + title: 'title', + description: 'descript', + security_token: 'key', + file_size: 'file_size', + key: 'key' + }, + { + file_name: 2, + file_type: 'type', + title: 'title', + description: 'descript', + security_token: 'key', + file_size: 'file_size', + key: 'key' + } + ]; + + before(() => { + data = new GetAttachmentsData(attachmentDetails); + }); + + it('sets all fields', function () { + expect(data.attachmentDetails).to.eql([ + { + file_name: 1, + file_type: 'type', + title: 'title', + description: 'descript', + key: '', + file_size: 'file_size', + is_secure: 'true' + }, + { + file_name: 2, + file_type: 'type', + title: 'title', + description: 'descript', + key: '', + file_size: 'file_size', + is_secure: 'true' + } + ]); + }); + }); +}); + +describe('GetReportAttachmentsData', () => { + describe('No values provided', () => { + it('sets attachmentDetails', function () { + const data: GetReportAttachmentsData = new GetReportAttachmentsData((null as unknown) as any[]); + + expect(data.attachmentDetails).to.eql([]); + }); + }); + + describe('Empty arrays as values provided', () => { + it('sets attachmentDetails', function () { + const data: GetReportAttachmentsData = new GetReportAttachmentsData([]); + + expect(data.attachmentDetails).to.eql([]); + }); + }); + + describe('some attachmentDetails asdasdsadsasd values provided', () => { + it('sets file_name', function () { + const attachmentDetails = [{ file_name: 1 }, { file_name: 2 }]; + + const data: GetReportAttachmentsData = new GetReportAttachmentsData(attachmentDetails); + expect(data.attachmentDetails).to.eql([ + { + file_name: 1, + title: undefined, + year: undefined, + description: undefined, + key: undefined, + file_size: undefined, + is_secure: 'false' + }, + { + file_name: 2, + title: undefined, + year: undefined, + description: undefined, + key: undefined, + file_size: undefined, + is_secure: 'false' + } + ]); + }); + }); + + describe('all attachmentDetails values provided', () => { + it('sets all fields', function () { + const attachmentDetails = [ + { + file_name: 1, + title: 'title', + year: '1', + description: 'descript', + security_token: 'key', + file_size: 'size', + key: 'key', + authors: [{ author: 'author' }] + }, + { + file_name: 2, + file_type: 'type', + title: 'title', + year: '2', + description: 'descript', + security_token: 'key', + file_size: 'size', + key: 'key', + authors: [{ author: 'author' }] + } + ]; + const data: GetReportAttachmentsData = new GetReportAttachmentsData(attachmentDetails); + + expect(data.attachmentDetails).to.eql([ + { + file_name: 1, + title: 'title', + year: '1', + description: 'descript', + key: '', + file_size: 'size', + is_secure: 'true', + authors: [{ author: 'author' }] + }, + { + file_name: 2, + title: 'title', + year: '2', + description: 'descript', + key: '', + file_size: 'size', + is_secure: 'true', + authors: [{ author: 'author' }] + } + ]); + }); + }); +}); diff --git a/api/src/models/project-view.ts b/api/src/models/project-view.ts index a577f15c42..dd2d26be31 100644 --- a/api/src/models/project-view.ts +++ b/api/src/models/project-view.ts @@ -5,7 +5,6 @@ import { COMPLETION_STATUS } from '../constants/status'; export interface IGetProject { id: number; coordinator: GetCoordinatorData; - permit: GetPermitData; project: GetProjectData; objectives: GetObjectivesData; location: GetLocationData; @@ -29,7 +28,6 @@ export class GetProjectData { end_date: string; comments: string; completion_status: string; - publish_date: string; revision_count: number; constructor(projectData?: any, activityData?: any[]) { @@ -46,7 +44,6 @@ export class GetProjectData { moment(projectData.end_date).endOf('day').isBefore(moment()) && COMPLETION_STATUS.COMPLETED) || COMPLETION_STATUS.ACTIVE; - this.publish_date = String(projectData?.publish_date || ''); this.revision_count = projectData?.revision_count ?? null; } } @@ -93,33 +90,6 @@ export class GetCoordinatorData { } } -export interface IGetPermit { - permit_number: string; - permit_type: string; -} - -/** - * Pre-processes GET /projects/{id} permit data - * - * @export - * @class GetPermitData - */ -export class GetPermitData { - permits: IGetPermit[]; - - constructor(permitData?: any[]) { - this.permits = - (permitData?.length && - permitData.map((item: any) => { - return { - permit_number: item.number, - permit_type: item.type - }; - })) || - []; - } -} - /** * Pre-processes GET /projects/{id} location data * @@ -223,3 +193,84 @@ export class GetPartnershipsData { (stakeholder_partnerships?.length && stakeholder_partnerships.map((item: any) => item.partnership_name)) || []; } } + +interface IGetAttachmentsSource { + file_name: string; + file_type: string; + title: string; + description: string; + key: string; + file_size: string; + is_secure: string; +} + +/** + * Pre-processes GET /projects/{id} attachments data + * + * @export + * @class GetAttachmentsData + */ +export class GetAttachmentsData { + attachmentDetails: IGetAttachmentsSource[]; + + constructor(attachments?: any[]) { + this.attachmentDetails = + (attachments?.length && + attachments.map((item: any) => { + return { + file_name: item.file_name, + file_type: item.file_type, + title: item.title, + description: item.description, + key: item.security_token ? '' : item.key, + file_size: item.file_size, + is_secure: item.security_token ? 'true' : 'false' + }; + })) || + []; + } +} + +interface IGetReportAttachmentsSource { + file_name: string; + title: string; + year: string; + description: string; + key: string; + file_size: string; + is_secure: string; + authors?: { author: string }[]; +} + +/** + * Pre-processes GET /projects/{id} report attachments data + * + * @export + * @class GetReportAttachmentsData + */ +export class GetReportAttachmentsData { + attachmentDetails: IGetReportAttachmentsSource[]; + + constructor(attachments?: any[]) { + this.attachmentDetails = + (attachments?.length && + attachments.map((item: any) => { + const attachmentItem = { + file_name: item.file_name, + title: item.title, + year: item.year, + description: item.description, + key: item.security_token ? '' : item.key, + file_size: item.file_size, + is_secure: item.security_token ? 'true' : 'false' + }; + + if (item.authors?.length) { + attachmentItem['authors'] = item.authors; + } + + return attachmentItem; + })) || + []; + } +} diff --git a/api/src/models/public/project.ts b/api/src/models/public/project.ts index e548491774..94e3a3f426 100644 --- a/api/src/models/public/project.ts +++ b/api/src/models/public/project.ts @@ -4,7 +4,7 @@ import { getLogger } from '../../utils/logger'; const defaultLog = getLogger('models/public/project'); /** - * Pre-processes GET /projects/{id} coordinator data for public (published) projects + * Pre-processes GET /projects/{id} coordinator data for public projects * * @export * @class GetPublicCoordinatorData @@ -30,7 +30,7 @@ export class GetPublicCoordinatorData { } /** - * Pre-processes GET public (published) project attachments data + * Pre-processes GET public project attachments data * * @export * @class GetPublicAttachmentsData diff --git a/api/src/models/summaryresults-create.ts b/api/src/models/summaryresults-create.ts index 34d8ecf144..e07eefafbc 100644 --- a/api/src/models/summaryresults-create.ts +++ b/api/src/models/summaryresults-create.ts @@ -10,38 +10,51 @@ const defaultLog = getLogger('models/summary-results-create'); */ export class PostSummaryDetails { study_area_id: string; + population_unit: string; + block_sample_unit_id: string; parameter: string; stratum: string; - parameter_value: number; - parameter_estimate: number; + observed: number; + estimated: number; + sightability_model: string; + sightability_correction_factor: number; standard_error: number; coefficient_variation: number; confidence_level_percent: number; - confidence_limit_upper: number; confidence_limit_lower: number; + confidence_limit_upper: number; total_area_survey_sqm: number; - kilometres_surveyed: number; - sightability_model: string; + area_flown: number; + total_kilometers_surveyed: number; + best_parameter_flag: string; outlier_blocks_removed: string; - analysis_method: string; + total_marked_animals_observed: number; + marked_animals_available: number; + parameter_comments: number; constructor(obj?: any) { defaultLog.debug({ label: 'PostSummaryDetails', message: 'params', obj }); - this.study_area_id = obj?.study_area_id || null; + this.population_unit = obj?.population_unit || null; + this.block_sample_unit_id = obj?.block_sample_unit_id || null; this.parameter = obj?.parameter || null; this.stratum = obj?.stratum || null; - this.parameter_value = obj?.parameter_value || null; - this.parameter_estimate = obj?.parameter_estimate || null; + this.observed = obj?.observed || null; + this.estimated = obj?.estimated || null; + this.sightability_model = obj?.sightability_model || null; + this.sightability_correction_factor = obj?.sightability_correction_factor || null; this.standard_error = obj?.standard_error || null; this.coefficient_variation = obj?.coefficient_variation || null; this.confidence_level_percent = obj?.confidence_level_percent || null; - this.confidence_limit_upper = obj?.confidence_limit_upper || null; this.confidence_limit_lower = obj?.confidence_limit_lower || null; - this.total_area_survey_sqm = obj?.area || null; - this.kilometres_surveyed = obj?.area_flown || null; - this.sightability_model = obj?.sightability_model || null; + this.confidence_limit_upper = obj?.confidence_limit_upper || null; + this.total_area_survey_sqm = obj?.total_area_survey_sqm || null; + this.area_flown = obj?.area_flown || null; + this.total_kilometers_surveyed = obj?.total_kilometers_surveyed || null; + this.best_parameter_flag = obj?.best_parameter_flag || null; this.outlier_blocks_removed = obj?.outlier_blocks_removed || null; - this.analysis_method = obj?.analysis_method || null; + this.total_marked_animals_observed = obj?.total_marked_animals_observed || null; + this.marked_animals_available = obj?.marked_animals_available || null; + this.parameter_comments = obj?.parameter_comments || null; } } diff --git a/api/src/models/survey-create.test.ts b/api/src/models/survey-create.test.ts index dbc7bbec8d..0024bd9876 100644 --- a/api/src/models/survey-create.test.ts +++ b/api/src/models/survey-create.test.ts @@ -218,12 +218,8 @@ describe('PostPermitData', () => { data = new PostPermitData(null); }); - it('sets permit_number', () => { - expect(data.permit_number).to.eql(null); - }); - - it('sets ancillary_species', () => { - expect(data.permit_type).to.eql(null); + it('sets permits', () => { + expect(data.permits).to.eql([]); }); }); @@ -231,8 +227,12 @@ describe('PostPermitData', () => { let data: PostPermitData; const obj = { - permit_number: '12345', - permit_type: 'permit_type' + permits: [ + { + permit_number: '12345', + permit_type: 'permit_type' + } + ] }; before(() => { @@ -240,11 +240,11 @@ describe('PostPermitData', () => { }); it('sets permit_number', () => { - expect(data.permit_number).to.equal(obj.permit_number); + expect(data.permits[0].permit_number).to.equal(obj.permits[0].permit_number); }); it('sets permit_type', () => { - expect(data.permit_type).to.equal(obj.permit_type); + expect(data.permits[0].permit_type).to.equal(obj.permits[0].permit_type); }); }); }); diff --git a/api/src/models/survey-create.ts b/api/src/models/survey-create.ts index 260f4c5edd..6b02e39cea 100644 --- a/api/src/models/survey-create.ts +++ b/api/src/models/survey-create.ts @@ -49,12 +49,10 @@ export class PostSpeciesData { } export class PostPermitData { - permit_number: string; - permit_type: string; + permits: { permit_number: string; permit_type: string }[]; constructor(obj?: any) { - this.permit_number = obj?.permit_number || null; - this.permit_type = obj?.permit_type || null; + this.permits = obj?.permits || []; } } diff --git a/api/src/models/survey-update.test.ts b/api/src/models/survey-update.test.ts index bab8234439..f7cf17c3f9 100644 --- a/api/src/models/survey-update.test.ts +++ b/api/src/models/survey-update.test.ts @@ -218,12 +218,8 @@ describe('PutPermitData', () => { data = new PutSurveyPermitData(null); }); - it('sets permit_number', () => { - expect(data.permit_number).to.eql(null); - }); - - it('sets ancillary_species', () => { - expect(data.permit_type).to.eql(null); + it('sets permits', () => { + expect(data.permits).to.eql([]); }); }); @@ -231,20 +227,29 @@ describe('PutPermitData', () => { let data: PutSurveyPermitData; const obj = { - permit_number: '12345', - permit_type: 'permit_type' + permits: [ + { + permit_id: 1, + permit_number: '12345', + permit_type: 'permit_type' + } + ] }; before(() => { data = new PutSurveyPermitData(obj); }); + it('sets permit_id', () => { + expect(data.permits[0].permit_id).to.equal(obj.permits[0].permit_id); + }); + it('sets permit_number', () => { - expect(data.permit_number).to.equal('12345'); + expect(data.permits[0].permit_number).to.equal(obj.permits[0].permit_number); }); it('sets permit_type', () => { - expect(data.permit_type).to.equal('permit_type'); + expect(data.permits[0].permit_type).to.equal(obj.permits[0].permit_type); }); }); }); diff --git a/api/src/models/survey-update.ts b/api/src/models/survey-update.ts index 0ca90a9b2d..b4c6f7a6c0 100644 --- a/api/src/models/survey-update.ts +++ b/api/src/models/survey-update.ts @@ -50,12 +50,10 @@ export class PutSurveySpeciesData { } export class PutSurveyPermitData { - permit_number: string; - permit_type: string; + permits: { permit_id?: number; permit_number: string; permit_type: string }[]; constructor(obj?: any) { - this.permit_number = obj?.permit_number || null; - this.permit_type = obj?.permit_type || null; + this.permits = obj?.permits || []; } } diff --git a/api/src/models/survey-view.test.ts b/api/src/models/survey-view.test.ts index a2012c765e..df4d1718ba 100644 --- a/api/src/models/survey-view.test.ts +++ b/api/src/models/survey-view.test.ts @@ -1,9 +1,12 @@ import { expect } from 'chai'; import { describe } from 'mocha'; +import { IPermitModel } from '../repositories/permit-repository'; import { GetAncillarySpeciesData, + GetAttachmentsData, GetFocalSpeciesData, GetPermitData, + GetReportAttachmentsData, GetSurveyData, GetSurveyFundingSources, GetSurveyLocationData, @@ -31,6 +34,10 @@ describe('GetSurveyData', () => { expect(data.start_date).to.equal(null); }); + it('sets geojson', () => { + expect(data.geometry).to.eql([]); + }); + it('sets biologist_first_name', () => { expect(data.biologist_first_name).to.equal(''); }); @@ -48,7 +55,9 @@ describe('GetSurveyData', () => { end_date: '2020/04/04', start_date: '2020/03/03', lead_first_name: 'first', - lead_last_name: 'last' + geojson: [{ data: 'data' }], + lead_last_name: 'last', + revision_count: 'count' }; before(() => { @@ -67,6 +76,10 @@ describe('GetSurveyData', () => { expect(data.start_date).to.equal(obj.start_date); }); + it('sets geojson', () => { + expect(data.geometry).to.equal(obj.geojson); + }); + it('sets biologist_first_name', () => { expect(data.biologist_first_name).to.equal(obj.lead_first_name); }); @@ -74,6 +87,10 @@ describe('GetSurveyData', () => { it('sets biologist_last_name', () => { expect(data.biologist_last_name).to.equal(obj.lead_last_name); }); + + it('sets revision_count', function () { + expect(data.revision_count).to.equal('count'); + }); }); }); @@ -82,7 +99,7 @@ describe('GetFocalSpeciesData', () => { let data: GetFocalSpeciesData; before(() => { - data = new GetFocalSpeciesData([]); + data = new GetFocalSpeciesData(); }); it('sets focal_species', () => { @@ -121,7 +138,7 @@ describe('GetAncillarySpeciesData', () => { let data: GetAncillarySpeciesData; before(() => { - data = new GetAncillarySpeciesData([]); + data = new GetAncillarySpeciesData(); }); it('sets ancillary_species', () => { @@ -160,36 +177,39 @@ describe('GetPermitData', () => { let data: GetPermitData; before(() => { - data = new GetPermitData(null); + data = new GetPermitData(undefined); }); - it('sets permit_number', () => { - expect(data.permit_number).to.equal(''); - }); - - it('sets ancillary_species', () => { - expect(data.permit_type).to.equal(''); + it('sets permits', () => { + expect(data.permits).to.eql([]); }); }); describe('All values provided', () => { let data: GetPermitData; - const obj = { - number: '12345', - type: 'permit_type' - }; + const obj = [ + { + permit_id: 1, + number: '12345', + type: 'permit_type' + } + ] as IPermitModel[]; before(() => { data = new GetPermitData(obj); }); + it('sets permit_id', () => { + expect(data.permits[0].permit_id).to.equal(obj[0].permit_id); + }); + it('sets permit_number', () => { - expect(data.permit_number).to.equal(obj.number); + expect(data.permits[0].permit_number).to.equal(obj[0].number); }); it('sets permit_type', () => { - expect(data.permit_type).to.equal(obj.type); + expect(data.permits[0].permit_type).to.equal(obj[0].type); }); }); }); @@ -199,7 +219,7 @@ describe('GetSurveyFundingSources', () => { let data: GetSurveyFundingSources; before(() => { - data = new GetSurveyFundingSources([]); + data = new GetSurveyFundingSources(); }); it('sets funding_sources', () => { @@ -434,7 +454,8 @@ describe('GetSurveyPurposeAndMethodologyData', () => { field_method_id: 2, ecological_season_id: 3, vantage_ids: [4, 5], - surveyed_all_areas: true + surveyed_all_areas: true, + revision_count: 'count' }; before(() => { @@ -464,5 +485,218 @@ describe('GetSurveyPurposeAndMethodologyData', () => { it('sets surveyed_all_areas', () => { expect(data.surveyed_all_areas).to.eql('true'); }); + + it('sets revision_count', function () { + expect(data.revision_count).to.equal('count'); + }); + }); +}); + +describe('GetAttachmentsData', () => { + describe('No values provided', () => { + let data: GetAttachmentsData; + + before(() => { + data = new GetAttachmentsData((null as unknown) as any[]); + }); + + it('sets attachmentDetails', function () { + expect(data.attachmentDetails).to.eql([]); + }); + }); + + describe('Empty arrays as values provided', () => { + let data: GetAttachmentsData; + + before(() => { + data = new GetAttachmentsData([]); + }); + + it('sets attachmentDetails', function () { + expect(data.attachmentDetails).to.eql([]); + }); + }); + + describe('some attachmentDetails values provided', () => { + let data: GetAttachmentsData; + + const attachmentDetails = [{ file_name: 1 }, { file_name: 2 }]; + + before(() => { + data = new GetAttachmentsData(attachmentDetails); + }); + + it('sets file_name', function () { + expect(data.attachmentDetails).to.eql([ + { + file_name: 1, + file_type: undefined, + title: undefined, + description: undefined, + key: undefined, + file_size: undefined, + is_secure: 'false' + }, + { + file_name: 2, + file_type: undefined, + title: undefined, + description: undefined, + key: undefined, + file_size: undefined, + is_secure: 'false' + } + ]); + }); + }); + + describe('all attachmentDetails values provided', () => { + let data: GetAttachmentsData; + + const attachmentDetails = [ + { + file_name: 1, + file_type: 'type', + title: 'title', + description: 'descript', + security_token: 'key', + file_size: 'file_size', + key: 'key' + }, + { + file_name: 2, + file_type: 'type', + title: 'title', + description: 'descript', + security_token: 'key', + file_size: 'file_size', + key: 'key' + } + ]; + + before(() => { + data = new GetAttachmentsData(attachmentDetails); + }); + + it('sets all fields', function () { + expect(data.attachmentDetails).to.eql([ + { + file_name: 1, + file_type: 'type', + title: 'title', + description: 'descript', + key: '', + file_size: 'file_size', + is_secure: 'true' + }, + { + file_name: 2, + file_type: 'type', + title: 'title', + description: 'descript', + key: '', + file_size: 'file_size', + is_secure: 'true' + } + ]); + }); + }); +}); + +describe('GetReportAttachmentsData', () => { + describe('No values provided', () => { + it('sets attachmentDetails', function () { + const data: GetReportAttachmentsData = new GetReportAttachmentsData((null as unknown) as any[]); + + expect(data.attachmentDetails).to.eql([]); + }); + }); + + describe('Empty arrays as values provided', () => { + it('sets attachmentDetails', function () { + const data: GetReportAttachmentsData = new GetReportAttachmentsData([]); + + expect(data.attachmentDetails).to.eql([]); + }); + }); + + describe('some attachmentDetails asdasdsadsasd values provided', () => { + it('sets file_name', function () { + const attachmentDetails = [{ file_name: 1 }, { file_name: 2 }]; + + const data: GetReportAttachmentsData = new GetReportAttachmentsData(attachmentDetails); + expect(data.attachmentDetails).to.eql([ + { + file_name: 1, + title: undefined, + year: undefined, + description: undefined, + key: undefined, + file_size: undefined, + is_secure: 'false' + }, + { + file_name: 2, + title: undefined, + year: undefined, + description: undefined, + key: undefined, + file_size: undefined, + is_secure: 'false' + } + ]); + }); + }); + + describe('all attachmentDetails values provided', () => { + it('sets all fields', function () { + const attachmentDetails = [ + { + file_name: 1, + title: 'title', + year: '1', + description: 'descript', + security_token: 'key', + file_size: 'size', + key: 'key', + authors: [{ author: 'author' }] + }, + { + file_name: 2, + file_type: 'type', + title: 'title', + year: '2', + description: 'descript', + security_token: 'key', + file_size: 'size', + key: 'key', + authors: [{ author: 'author' }] + } + ]; + const data: GetReportAttachmentsData = new GetReportAttachmentsData(attachmentDetails); + + expect(data.attachmentDetails).to.eql([ + { + file_name: 1, + title: 'title', + year: '1', + description: 'descript', + key: '', + file_size: 'size', + is_secure: 'true', + authors: [{ author: 'author' }] + }, + { + file_name: 2, + title: 'title', + year: '2', + description: 'descript', + key: '', + file_size: 'size', + is_secure: 'true', + authors: [{ author: 'author' }] + } + ]); + }); }); }); diff --git a/api/src/models/survey-view.ts b/api/src/models/survey-view.ts index ea863ffacd..7d22fccd41 100644 --- a/api/src/models/survey-view.ts +++ b/api/src/models/survey-view.ts @@ -1,4 +1,5 @@ import { Feature } from 'geojson'; +import { IPermitModel } from '../repositories/permit-repository'; export type SurveyObject = { survey_details: GetSurveyData; @@ -18,7 +19,6 @@ export class GetSurveyData { end_date: string; biologist_first_name: string; biologist_last_name: string; - publish_date: string; survey_area_name: string; geometry: Feature[]; revision_count: number; @@ -29,7 +29,6 @@ export class GetSurveyData { this.survey_name = obj?.name || ''; this.start_date = obj?.start_date || null; this.end_date = obj?.end_date || null; - this.publish_date = String(obj?.publish_date || ''); this.geometry = (obj?.geojson?.length && obj.geojson) || []; this.biologist_first_name = obj?.lead_first_name || ''; this.biologist_last_name = obj?.lead_last_name || ''; @@ -70,12 +69,19 @@ export class GetAncillarySpeciesData { } } export class GetPermitData { - permit_number: number; - permit_type: string; + permits: { + permit_id: IPermitModel['permit_id']; + permit_number: IPermitModel['number']; + permit_type: IPermitModel['type']; + }[]; - constructor(obj?: any) { - this.permit_number = obj?.number || ''; - this.permit_type = obj?.type || ''; + constructor(obj?: IPermitModel[]) { + this.permits = + obj?.map((item) => ({ + permit_id: item.permit_id, + permit_number: item.number, + permit_type: item.type + })) || []; } } @@ -168,3 +174,84 @@ export class GetSurveyLocationData { this.geometry = (obj?.geojson?.length && obj.geojson) || []; } } + +interface IGetAttachmentsSource { + file_name: string; + file_type: string; + title: string; + description: string; + key: string; + file_size: string; + is_secure: string; +} + +/** + * Pre-processes GET /surveys/{id} attachments data + * + * @export + * @class GetAttachmentsData + */ +export class GetAttachmentsData { + attachmentDetails: IGetAttachmentsSource[]; + + constructor(attachments?: any[]) { + this.attachmentDetails = + (attachments?.length && + attachments.map((item: any) => { + return { + file_name: item.file_name, + file_type: item.file_type, + title: item.title, + description: item.description, + key: item.security_token ? '' : item.key, + file_size: item.file_size, + is_secure: item.security_token ? 'true' : 'false' + }; + })) || + []; + } +} + +interface IGetReportAttachmentsSource { + file_name: string; + title: string; + year: string; + description: string; + key: string; + file_size: string; + is_secure: string; + authors?: { author: string }[]; +} + +/** + * Pre-processes GET /surveys/{id} report attachments data + * + * @export + * @class GetReportAttachmentsData + */ +export class GetReportAttachmentsData { + attachmentDetails: IGetReportAttachmentsSource[]; + + constructor(attachments?: any[]) { + this.attachmentDetails = + (attachments?.length && + attachments.map((item: any) => { + const attachmentItem = { + file_name: item.file_name, + title: item.title, + year: item.year, + description: item.description, + key: item.security_token ? '' : item.key, + file_size: item.file_size, + is_secure: item.security_token ? 'true' : 'false' + }; + + if (item.authors?.length) { + attachmentItem['authors'] = item.authors; + } + + return attachmentItem; + })) || + []; + } +} diff --git a/api/src/openapi/schemas/permit-no-sampling.test.ts b/api/src/openapi/schemas/permit-no-sampling.test.ts deleted file mode 100644 index 74c181de31..0000000000 --- a/api/src/openapi/schemas/permit-no-sampling.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -import Ajv from 'ajv'; -import { expect } from 'chai'; -import { describe } from 'mocha'; -import { permitNoSamplingPostBody, permitNoSamplingResponseBody } from './permit-no-sampling'; - -describe('permitNoSamplingPostBody', () => { - const ajv = new Ajv(); - - it('is valid openapi v3 schema', () => { - expect(ajv.validateSchema(permitNoSamplingPostBody)).to.be.true; - }); -}); - -describe('permitNoSamplingResponseBody', () => { - const ajv = new Ajv(); - - it('is valid openapi v3 schema', () => { - expect(ajv.validateSchema(permitNoSamplingResponseBody)).to.be.true; - }); -}); diff --git a/api/src/openapi/schemas/permit-no-sampling.ts b/api/src/openapi/schemas/permit-no-sampling.ts deleted file mode 100644 index e256cdbe93..0000000000 --- a/api/src/openapi/schemas/permit-no-sampling.ts +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Permit no sampling endpoint post body openapi schema. - */ -export const permitNoSamplingPostBody = { - title: 'Non-Sampling Permit Post Object', - type: 'object', - required: ['coordinator', 'permit'], - properties: { - coordinator: { - title: 'Coordinator', - type: 'object', - required: ['first_name', 'last_name', 'email_address', 'coordinator_agency'], - properties: { - first_name: { - type: 'string' - }, - last_name: { - type: 'string' - }, - email_address: { - type: 'string' - }, - coordinator_agency: { - type: 'string' - }, - share_contact_details: { - type: 'string', - enum: ['true', 'false'] - } - } - }, - permit: { - title: 'Non-sampling permits', - type: 'object', - required: ['permits'], - properties: { - permits: { - type: 'array', - items: { - title: 'Non-sampling permit', - type: 'object', - required: ['permit_number'], - additionalProperties: true, - properties: { - permit_number: { - type: 'string' - } - } - } - } - } - } - } -}; - -/** - * Permit no sampling endpoint response body openapi schema. - */ -export const permitNoSamplingResponseBody = { - title: 'Permit no sampling Response Object', - type: 'object', - required: ['ids'], - properties: { - ids: { - type: 'array', - title: 'Permit no sampling ids', - items: { - type: 'number' - } - } - } -}; diff --git a/api/src/openapi/schemas/project.ts b/api/src/openapi/schemas/project.ts index 8c48824117..aba32f4c71 100644 --- a/api/src/openapi/schemas/project.ts +++ b/api/src/openapi/schemas/project.ts @@ -4,7 +4,7 @@ export const projectCreatePostRequestObject = { title: 'Project post request object', type: 'object', - required: ['coordinator', 'permit', 'project', 'location', 'iucn', 'funding'], + required: ['coordinator', 'project', 'location', 'iucn', 'funding'], properties: { coordinator: { title: 'Project coordinator', @@ -29,24 +29,6 @@ export const projectCreatePostRequestObject = { } } }, - permit: { - title: 'Project permits', - type: 'object', - properties: { - permits: { - type: 'array', - items: { - title: 'Project permit', - type: 'object', - properties: { - permit_number: { - type: 'string' - } - } - } - } - } - }, project: { title: 'Project details', type: 'object', @@ -175,7 +157,6 @@ const projectUpdateProperties = { revision_count: { type: 'number' } } }, - permit: { type: 'object', properties: {} }, project: { type: 'object', properties: {} }, objectives: { type: 'object', properties: {} }, location: { type: 'object', properties: {} }, diff --git a/api/src/paths/dwc/eml.test.ts b/api/src/paths/dwc/eml.test.ts index faf27db3e7..d5a035e0d5 100644 --- a/api/src/paths/dwc/eml.test.ts +++ b/api/src/paths/dwc/eml.test.ts @@ -29,7 +29,7 @@ describe('getProjectEml', () => { expect.fail(); } catch (actualError) { expect((actualError as HTTPError).status).to.equal(400); - expect((actualError as HTTPError).message).to.equal('Failed to build SQL get statement'); + expect((actualError as HTTPError).message).to.equal('Failed to get project objectives data'); } }); diff --git a/api/src/paths/dwc/validate.test.ts b/api/src/paths/dwc/validate.test.ts index 59ba5f74a4..106615bcc2 100644 --- a/api/src/paths/dwc/validate.test.ts +++ b/api/src/paths/dwc/validate.test.ts @@ -97,7 +97,7 @@ describe('getOccurrenceSubmission', () => { } }); - // TODO update this test as teh s3 key is not part of the `getOccurrenceSubmission` step now + // TODO update this test as the s3 key is not part of the `getOccurrenceSubmission` step now it('should set occurrence_submission in the request on success', async () => { const nextSpy = sinon.spy(); const mockQuery = sinon.stub(); diff --git a/api/src/paths/permit/create-no-sampling.test.ts b/api/src/paths/permit/create-no-sampling.test.ts deleted file mode 100644 index 94dac152d7..0000000000 --- a/api/src/paths/permit/create-no-sampling.test.ts +++ /dev/null @@ -1,60 +0,0 @@ -import chai, { expect } from 'chai'; -import { describe } from 'mocha'; -import sinon from 'sinon'; -import sinonChai from 'sinon-chai'; -import * as db from '../../database/db'; -import { HTTPError } from '../../errors/custom-error'; -import { PermitService } from '../../services/permit-service'; -import { getMockDBConnection, getRequestHandlerMocks } from '../../__mocks__/db'; -import { createNoSamplePermits } from './create-no-sampling'; - -chai.use(sinonChai); - -describe('create-no-sampling', () => { - describe('createNoSamplePermits', () => { - afterEach(() => { - sinon.restore(); - }); - - it('catches error, calls rollback, and re-throws error', async () => { - const dbConnectionObj = getMockDBConnection({ rollback: sinon.stub(), release: sinon.stub() }); - sinon.stub(db, 'getDBConnection').returns(dbConnectionObj); - - sinon.stub(PermitService.prototype, 'createNoSamplePermits').rejects(new Error('a test error')); - - const { mockReq, mockRes, mockNext } = getRequestHandlerMocks(); - - try { - const requestHandler = createNoSamplePermits(); - - await requestHandler(mockReq, mockRes, mockNext); - expect.fail(); - } catch (actualError) { - expect(dbConnectionObj.rollback).to.have.been.called; - expect(dbConnectionObj.release).to.have.been.called; - expect((actualError as HTTPError).message).to.equal('a test error'); - } - }); - - it('creates a new non sample permit', async () => { - const dbConnectionObj = getMockDBConnection(); - - sinon.stub(db, 'getDBConnection').returns(dbConnectionObj); - - sinon.stub(PermitService.prototype, 'createNoSamplePermits').resolves([1]); - - const { mockReq, mockRes, mockNext } = getRequestHandlerMocks(); - - try { - const requestHandler = createNoSamplePermits(); - - await requestHandler(mockReq, mockRes, mockNext); - } catch (actualError) { - expect.fail(); - } - - expect(mockRes.statusValue).to.equal(200); - expect(mockRes.jsonValue).to.eql({ ids: [1] }); - }); - }); -}); diff --git a/api/src/paths/permit/create-no-sampling.ts b/api/src/paths/permit/create-no-sampling.ts deleted file mode 100644 index 643985536a..0000000000 --- a/api/src/paths/permit/create-no-sampling.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { RequestHandler } from 'express'; -import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../constants/roles'; -import { getDBConnection } from '../../database/db'; -import { permitNoSamplingPostBody, permitNoSamplingResponseBody } from '../../openapi/schemas/permit-no-sampling'; -import { authorizeRequestHandler } from '../../request-handlers/security/authorization'; -import { PermitService } from '../../services/permit-service'; -import { getLogger } from '../../utils/logger'; - -const defaultLog = getLogger('/api/permit/create-no-sampling'); - -export const POST: Operation = [ - authorizeRequestHandler((req) => { - return { - or: [ - { - validSystemRoles: [SYSTEM_ROLE.SYSTEM_ADMIN, SYSTEM_ROLE.PROJECT_CREATOR], - discriminator: 'SystemRole' - }, - { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD], - projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' - } - ] - }; - }), - createNoSamplePermits() -]; - -POST.apiDoc = { - description: 'Creates new no sample permit records.', - tags: ['no-sample-permit'], - security: [ - { - Bearer: [] - } - ], - requestBody: { - description: 'No sample permits post request object.', - content: { - 'application/json': { - schema: { - ...(permitNoSamplingPostBody as object) - } - } - } - }, - responses: { - 200: { - description: 'No sample permits response object.', - content: { - 'application/json': { - schema: { - ...(permitNoSamplingResponseBody as object) - } - } - } - }, - 400: { - $ref: '#/components/responses/400' - }, - 401: { - $ref: '#/components/responses/401' - }, - 403: { - $ref: '#/components/responses/403' - }, - 500: { - $ref: '#/components/responses/500' - }, - default: { - $ref: '#/components/responses/default' - } - } -}; - -/** - * Creates new no sample permit records. - * - * @returns {RequestHandler} - */ -export function createNoSamplePermits(): RequestHandler { - return async (req, res) => { - const connection = getDBConnection(req['keycloak_token']); - - try { - await connection.open(); - const permitService = new PermitService(connection); - - const result = await permitService.createNoSamplePermits(req.body); - - await connection.commit(); - - return res.status(200).json({ ids: result }); - } catch (error) { - defaultLog.error({ label: 'createNoSamplePermits', message: 'error', error }); - await connection.rollback(); - throw error; - } finally { - connection.release(); - } - }; -} diff --git a/api/src/paths/permit/get-no-sampling.test.ts b/api/src/paths/permit/get-no-sampling.test.ts deleted file mode 100644 index 585132107e..0000000000 --- a/api/src/paths/permit/get-no-sampling.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import chai, { expect } from 'chai'; -import { describe } from 'mocha'; -import sinon from 'sinon'; -import sinonChai from 'sinon-chai'; -import * as db from '../../database/db'; -import { HTTPError } from '../../errors/custom-error'; -import { PermitService } from '../../services/permit-service'; -import { getMockDBConnection, getRequestHandlerMocks } from '../../__mocks__/db'; -import { getNonSamplingPermits } from './get-no-sampling'; - -chai.use(sinonChai); - -describe('get-no-sampling', () => { - describe('getNonSamplingPermits', () => { - afterEach(() => { - sinon.restore(); - }); - - it('catches error, calls rollback, and re-throws error', async () => { - const dbConnectionObj = getMockDBConnection({ rollback: sinon.stub(), release: sinon.stub() }); - sinon.stub(db, 'getDBConnection').returns(dbConnectionObj); - - sinon.stub(PermitService.prototype, 'getNonSamplingPermits').rejects(new Error('a test error')); - - const { mockReq, mockRes, mockNext } = getRequestHandlerMocks(); - - try { - const requestHandler = getNonSamplingPermits(); - - await requestHandler(mockReq, mockRes, mockNext); - expect.fail(); - } catch (actualError) { - expect(dbConnectionObj.rollback).to.have.been.called; - expect(dbConnectionObj.release).to.have.been.called; - expect((actualError as HTTPError).message).to.equal('a test error'); - } - }); - - it('gets non sample permits', async () => { - const dbConnectionObj = getMockDBConnection(); - - sinon.stub(db, 'getDBConnection').returns(dbConnectionObj); - - sinon - .stub(PermitService.prototype, 'getNonSamplingPermits') - .resolves([{ permit_id: '1', number: '2', type: '3' }]); - - const { mockReq, mockRes, mockNext } = getRequestHandlerMocks(); - - try { - const requestHandler = getNonSamplingPermits(); - - await requestHandler(mockReq, mockRes, mockNext); - } catch (actualError) { - expect.fail(); - } - - expect(mockRes.statusValue).to.equal(200); - expect(mockRes.jsonValue).to.eql([{ permit_id: '1', number: '2', type: '3' }]); - }); - }); -}); diff --git a/api/src/paths/permit/get-no-sampling.ts b/api/src/paths/permit/get-no-sampling.ts deleted file mode 100644 index ac0a7d583e..0000000000 --- a/api/src/paths/permit/get-no-sampling.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { RequestHandler } from 'express'; -import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../constants/roles'; -import { getDBConnection } from '../../database/db'; -import { authorizeRequestHandler } from '../../request-handlers/security/authorization'; -import { PermitService } from '../../services/permit-service'; -import { getLogger } from '../../utils/logger'; - -const defaultLog = getLogger('/api/permit/get-no-sampling'); - -export const GET: Operation = [ - authorizeRequestHandler((req) => { - return { - or: [ - { - validSystemRoles: [SYSTEM_ROLE.SYSTEM_ADMIN, SYSTEM_ROLE.PROJECT_CREATOR], - discriminator: 'SystemRole' - }, - { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR, PROJECT_ROLE.PROJECT_VIEWER], - projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' - } - ] - }; - }), - getNonSamplingPermits() -]; - -GET.apiDoc = { - description: 'Fetches a list of non-sampling permits.', - tags: ['non-sampling-permits'], - security: [ - { - Bearer: [] - } - ], - responses: { - 200: { - description: 'Non-sampling permits get response array.', - content: { - 'application/json': { - schema: { - type: 'array', - items: { - title: 'Non-sampling permit Get Response Object', - type: 'object', - properties: { - id: { - type: 'number' - }, - number: { - type: 'string' - }, - type: { - type: 'string' - } - } - }, - description: 'Non-sampling permits' - } - } - } - }, - 400: { - $ref: '#/components/responses/400' - }, - 401: { - $ref: '#/components/responses/401' - }, - 403: { - $ref: '#/components/responses/403' - }, - 500: { - $ref: '#/components/responses/500' - }, - default: { - $ref: '#/components/responses/default' - } - } -}; - -export function getNonSamplingPermits(): RequestHandler { - return async (req, res) => { - const connection = getDBConnection(req['keycloak_token']); - - try { - await connection.open(); - - const systemUserId = connection.systemUserId(); - - const permitService = new PermitService(connection); - - const getNonSamplingPermitsData = await permitService.getNonSamplingPermits(systemUserId); - - await connection.commit(); - - return res.status(200).json(getNonSamplingPermitsData); - } catch (error) { - defaultLog.error({ label: 'getNonSamplingPermits', message: 'error', error }); - await connection.rollback(); - throw error; - } finally { - connection.release(); - } - }; -} diff --git a/api/src/paths/permit/list.test.ts b/api/src/paths/permit/list.test.ts index 9214217a58..be33149073 100644 --- a/api/src/paths/permit/list.test.ts +++ b/api/src/paths/permit/list.test.ts @@ -1,64 +1,88 @@ -import chai, { expect } from 'chai'; +import { expect } from 'chai'; import { describe } from 'mocha'; import sinon from 'sinon'; -import sinonChai from 'sinon-chai'; import * as db from '../../database/db'; -import { HTTPError } from '../../errors/custom-error'; +import { ApiGeneralError } from '../../errors/custom-error'; +import { IPermitModel } from '../../repositories/permit-repository'; import { PermitService } from '../../services/permit-service'; import { getMockDBConnection, getRequestHandlerMocks } from '../../__mocks__/db'; -import { getAllPermits } from './list'; +import * as list from './list'; -chai.use(sinonChai); +describe('listUserPermits', () => { + afterEach(() => { + sinon.restore(); + }); -describe('permit-list', () => { - describe('getAllPermits', () => { - afterEach(() => { - sinon.restore(); - }); + it('returns a list of permits', async () => { + const dbConnectionObj = getMockDBConnection({ systemUserId: () => 3 }); - it('catches error, calls rollback, and re-throws error', async () => { - const dbConnectionObj = getMockDBConnection({ rollback: sinon.stub(), release: sinon.stub() }); - sinon.stub(db, 'getDBConnection').returns(dbConnectionObj); + sinon.stub(db, 'getDBConnection').returns(dbConnectionObj); - sinon.stub(PermitService.prototype, 'getAllPermits').rejects(new Error('a test error')); + const { mockReq, mockRes, mockNext } = getRequestHandlerMocks(); - const { mockReq, mockRes, mockNext } = getRequestHandlerMocks(); + const mockPermit1: IPermitModel = { + permit_id: 1, + survey_id: 1, + number: '123456', + type: 'permit type', + create_date: new Date().toISOString(), + create_user: 1, + update_date: null, + update_user: null, + revision_count: 0 + }; - try { - const requestHandler = getAllPermits(); + const mockPermit2: IPermitModel = { + permit_id: 2, + survey_id: 2, + number: '654321', + type: 'permit type', + create_date: new Date().toISOString(), + create_user: 2, + update_date: new Date().toISOString(), + update_user: 2, + revision_count: 1 + }; - await requestHandler(mockReq, mockRes, mockNext); - expect.fail(); - } catch (actualError) { - expect(dbConnectionObj.rollback).to.have.been.called; - expect(dbConnectionObj.release).to.have.been.called; - expect((actualError as HTTPError).message).to.equal('a test error'); - } - }); + const getPermitByUserStub = sinon + .stub(PermitService.prototype, 'getPermitByUser') + .resolves([mockPermit1, mockPermit2]); + + const requestHandler = list.listUserPermits(); + + await requestHandler(mockReq, mockRes, mockNext); - it('gets non sample permits', async () => { - const dbConnectionObj = getMockDBConnection(); + expect(getPermitByUserStub).to.have.been.calledOnceWith(3); - sinon.stub(db, 'getDBConnection').returns(dbConnectionObj); + expect(mockRes.statusValue).to.equal(200); + expect(mockRes.jsonValue).to.eql({ permits: [mockPermit1, mockPermit2] }); + }); - sinon - .stub(PermitService.prototype, 'getAllPermits') - .resolves([{ id: '1', number: '2', type: '3', coordinator_agency: '4', project_name: '5' }]); + it('should throw an error if getPermitByUser throws an Error', async () => { + const dbConnectionObj = getMockDBConnection({ + commit: sinon.stub(), + rollback: sinon.stub(), + release: sinon.stub(), + systemUserId: () => 3 + }); - const { mockReq, mockRes, mockNext } = getRequestHandlerMocks(); + sinon.stub(db, 'getDBConnection').returns(dbConnectionObj); - try { - const requestHandler = getAllPermits(); + const { mockReq, mockRes, mockNext } = getRequestHandlerMocks(); - await requestHandler(mockReq, mockRes, mockNext); - } catch (actualError) { - expect.fail(); - } + mockReq.query = {}; - expect(mockRes.statusValue).to.equal(200); - expect(mockRes.jsonValue).to.eql([ - { id: '1', number: '2', type: '3', coordinator_agency: '4', project_name: '5' } - ]); - }); + sinon.stub(PermitService.prototype, 'getPermitByUser').throws(('error' as unknown) as ApiGeneralError); + + try { + const requestHandler = list.listUserPermits(); + + await requestHandler(mockReq, mockRes, mockNext); + expect.fail(); + } catch (actualError) { + expect(dbConnectionObj.commit).to.not.be.called; + expect(dbConnectionObj.rollback).to.be.calledOnce; + expect(dbConnectionObj.release).to.be.calledOnce; + } }); }); diff --git a/api/src/paths/permit/list.ts b/api/src/paths/permit/list.ts index 42621829b7..fb59e8f823 100644 --- a/api/src/paths/permit/list.ts +++ b/api/src/paths/permit/list.ts @@ -1,34 +1,29 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../../constants/roles'; import { getDBConnection } from '../../database/db'; +import { HTTP400 } from '../../errors/custom-error'; +import { IPermitModel } from '../../repositories/permit-repository'; import { authorizeRequestHandler } from '../../request-handlers/security/authorization'; import { PermitService } from '../../services/permit-service'; import { getLogger } from '../../utils/logger'; -const defaultLog = getLogger('/api/permits/list'); +const defaultLog = getLogger('/api/permits'); export const GET: Operation = [ - authorizeRequestHandler((req) => { + authorizeRequestHandler(() => { return { - or: [ + and: [ { - validSystemRoles: [SYSTEM_ROLE.SYSTEM_ADMIN, SYSTEM_ROLE.PROJECT_CREATOR], - discriminator: 'SystemRole' - }, - { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR, PROJECT_ROLE.PROJECT_VIEWER], - projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' + discriminator: 'SystemUser' } ] }; }), - getAllPermits() + listUserPermits() ]; GET.apiDoc = { - description: 'Fetches a list of all permits by system user id.', + description: 'Fetches a list of permits that the logged in user is associated with.', tags: ['permits'], security: [ { @@ -37,31 +32,69 @@ GET.apiDoc = { ], responses: { 200: { - description: 'Permits get response array.', + description: 'Permits list response.', content: { 'application/json': { schema: { - type: 'array', - items: { - title: 'Permit Get Response Object', - type: 'object', - properties: { - number: { - type: 'string' - }, - type: { - type: 'string' - }, - coordinator_agency: { - type: 'string' - }, - project_name: { - type: 'string', - nullable: true + type: 'object', + required: ['permits'], + properties: { + permits: { + type: 'array', + items: { + type: 'object', + required: [ + 'permit_id', + 'survey_id', + 'number', + 'type', + 'create_date', + 'create_user', + 'update_date', + 'update_user', + 'revision_count' + ], + properties: { + permit_id: { + type: 'integer', + minimum: 1 + }, + survey_id: { + type: 'integer', + minimum: 1, + nullable: true + }, + number: { + type: 'string' + }, + type: { + type: 'string' + }, + create_date: { + oneOf: [{ type: 'object' }, { type: 'string', format: 'date' }], + description: 'ISO 8601 date string for the permit create_date' + }, + create_user: { + type: 'integer', + minimum: 1 + }, + update_date: { + oneOf: [{ type: 'object' }, { type: 'string', format: 'date' }], + description: 'ISO 8601 date string for the permit update_date', + nullable: true + }, + update_user: { + type: 'integer', + nullable: true + }, + revision_count: { + type: 'integer', + minimum: 0 + } + } } } - }, - description: 'All permits in the permits table for the appropriate system user' + } } } } @@ -73,7 +106,7 @@ GET.apiDoc = { $ref: '#/components/responses/401' }, 403: { - $ref: '#/components/responses/403' + $ref: '#/components/responses/401' }, 500: { $ref: '#/components/responses/500' @@ -84,7 +117,7 @@ GET.apiDoc = { } }; -export function getAllPermits(): RequestHandler { +export function listUserPermits(): RequestHandler { return async (req, res) => { const connection = getDBConnection(req['keycloak_token']); @@ -93,15 +126,19 @@ export function getAllPermits(): RequestHandler { const systemUserId = connection.systemUserId(); - const permitService = new PermitService(connection); + if (!systemUserId) { + throw new HTTP400('Failed to identify system user ID'); + } + + const userPermitService = new PermitService(connection); - const getPermitsData = await permitService.getAllPermits(systemUserId); + const permits: IPermitModel[] = await userPermitService.getPermitByUser(systemUserId); await connection.commit(); - return res.status(200).json(getPermitsData); + res.status(200).json({ permits: permits }); } catch (error) { - defaultLog.error({ label: 'getAllPermits', message: 'error', error }); + defaultLog.error({ label: 'listUserPermits', message: 'error', error }); await connection.rollback(); throw error; } finally { diff --git a/api/src/paths/project/create.test.ts b/api/src/paths/project/create.test.ts index 246391f48e..7a17ab5f85 100644 --- a/api/src/paths/project/create.test.ts +++ b/api/src/paths/project/create.test.ts @@ -5,6 +5,7 @@ import sinon from 'sinon'; import sinonChai from 'sinon-chai'; import * as db from '../../database/db'; import { HTTPError } from '../../errors/custom-error'; +import { PlatformService } from '../../services/platform-service'; import { ProjectService } from '../../services/project-service'; import { getMockDBConnection, getRequestHandlerMocks } from '../../__mocks__/db'; import { createProject, POST } from './create'; @@ -32,6 +33,8 @@ describe('create', () => { sinon.stub(ProjectService.prototype, 'createProject').resolves(1); + sinon.stub(PlatformService.prototype, 'submitDwCAMetadataPackage').resolves(); + const { mockReq, mockRes, mockNext } = getRequestHandlerMocks(); try { diff --git a/api/src/paths/project/create.ts b/api/src/paths/project/create.ts index 970bfb7453..854e3641a1 100644 --- a/api/src/paths/project/create.ts +++ b/api/src/paths/project/create.ts @@ -5,6 +5,7 @@ import { getDBConnection } from '../../database/db'; import { PostProjectObject } from '../../models/project-create'; import { projectCreatePostRequestObject, projectIdResponseObject } from '../../openapi/schemas/project'; import { authorizeRequestHandler } from '../../request-handlers/security/authorization'; +import { PlatformService } from '../../services/platform-service'; import { ProjectService } from '../../services/project-service'; import { getLogger } from '../../utils/logger'; @@ -89,6 +90,14 @@ export function createProject(): RequestHandler { const projectId = await projectService.createProject(sanitizedProjectPostData); + try { + const platformService = new PlatformService(connection); + await platformService.submitDwCAMetadataPackage(projectId); + } catch (error) { + // Don't fail the rest of the endpoint if submitting metadata fails + defaultLog.error({ label: 'createProject->submitDwCAMetadataPackage', message: 'error', error }); + } + await connection.commit(); return res.status(200).json({ id: projectId }); diff --git a/api/src/paths/project/list.ts b/api/src/paths/project/list.ts index b47fda903e..779368af8d 100644 --- a/api/src/paths/project/list.ts +++ b/api/src/paths/project/list.ts @@ -40,10 +40,6 @@ GET.apiDoc = { type: 'string', nullable: true }, - permit_number: { - type: 'string', - nullable: true - }, project_type: { type: 'string', nullable: true diff --git a/api/src/paths/project/{projectId}/attachments/{attachmentId}/metadata/get.ts b/api/src/paths/project/{projectId}/attachments/{attachmentId}/metadata/get.ts index fbcc1b7773..eab9bdd645 100644 --- a/api/src/paths/project/{projectId}/attachments/{attachmentId}/metadata/get.ts +++ b/api/src/paths/project/{projectId}/attachments/{attachmentId}/metadata/get.ts @@ -38,7 +38,8 @@ GET.apiDoc = { in: 'path', name: 'projectId', schema: { - type: 'number' + type: 'integer', + minimum: 1 }, required: true }, @@ -46,7 +47,8 @@ GET.apiDoc = { in: 'path', name: 'attachmentId', schema: { - type: 'number' + type: 'integer', + minimum: 1 }, required: true } diff --git a/api/src/paths/project/{projectId}/delete.test.ts b/api/src/paths/project/{projectId}/delete.test.ts index 69e3571468..4e56a1d4ee 100644 --- a/api/src/paths/project/{projectId}/delete.test.ts +++ b/api/src/paths/project/{projectId}/delete.test.ts @@ -63,27 +63,6 @@ describe('deleteProject', () => { } }); - it('should throw a 400 error when no sql statement returned for getProjectSQL', async () => { - sinon.stub(db, 'getDBConnection').returns({ - ...dbConnectionObj, - systemUserId: () => { - return 20; - } - }); - - sinon.stub(project_queries, 'getProjectSQL').returns(null); - - try { - const result = delete_project.deleteProject(); - - await result(sampleReq, (null as unknown) as any, (null as unknown) as any); - expect.fail(); - } catch (actualError) { - expect((actualError as HTTPError).status).to.equal(400); - expect((actualError as HTTPError).message).to.equal('Failed to build SQL get statement'); - } - }); - it('should throw a 400 error when fails to get the project cause no rows', async () => { sinon.stub(db, 'getDBConnection').returns({ ...dbConnectionObj, @@ -140,42 +119,6 @@ describe('deleteProject', () => { } }); - it('should throw a 400 error when user has insufficient role to delete published project', async () => { - sinon.stub(db, 'getDBConnection').returns({ - ...dbConnectionObj, - systemUserId: () => { - return 20; - }, - query: async () => { - return { - rowCount: 1, - rows: [ - { - id: 1, - publish_date: 'some date' - } - ] - } as QueryResult; - } - }); - - try { - const result = delete_project.deleteProject(); - - await result( - { ...sampleReq, system_user: { role_names: [SYSTEM_ROLE.PROJECT_CREATOR] } }, - (null as unknown) as any, - (null as unknown) as any - ); - expect.fail(); - } catch (actualError) { - expect((actualError as HTTPError).status).to.equal(400); - expect((actualError as HTTPError).message).to.equal( - 'Cannot delete a published project if you are not a system administrator.' - ); - } - }); - it('should throw a 400 error when failed to get result for project attachments', async () => { const mockQuery = sinon.stub(); diff --git a/api/src/paths/project/{projectId}/delete.ts b/api/src/paths/project/{projectId}/delete.ts index ccef35a2f8..e46fd6d9de 100644 --- a/api/src/paths/project/{projectId}/delete.ts +++ b/api/src/paths/project/{projectId}/delete.ts @@ -73,14 +73,13 @@ export function deleteProject(): RequestHandler { const connection = getDBConnection(req['keycloak_token']); const projectId = Number(req.params.projectId); - const userRoles = req['system_user']['role_names']; try { await connection.open(); const projectService = new ProjectService(connection); - const resp = await projectService.deleteProject(projectId, userRoles); + const resp = await projectService.deleteProject(projectId); await connection.commit(); diff --git a/api/src/paths/project/{projectId}/funding-sources/add.test.ts b/api/src/paths/project/{projectId}/funding-sources/add.test.ts index b804a31679..b4c96e442f 100644 --- a/api/src/paths/project/{projectId}/funding-sources/add.test.ts +++ b/api/src/paths/project/{projectId}/funding-sources/add.test.ts @@ -6,6 +6,7 @@ import SQL from 'sql-template-strings'; import * as db from '../../../../database/db'; import { HTTPError } from '../../../../errors/custom-error'; import project_queries from '../../../../queries/project'; +import { PlatformService } from '../../../../services/platform-service'; import { getMockDBConnection } from '../../../../__mocks__/db'; import * as addFunding from './add'; @@ -170,6 +171,8 @@ describe('add a funding source', () => { sinon.stub(project_queries, 'postProjectFundingSourceSQL').returns(SQL`something`); + sinon.stub(PlatformService.prototype, 'submitDwCAMetadataPackage').resolves(); + const result = addFunding.addFundingSource(); await result(sampleReq, sampleRes as any, (null as unknown) as any); diff --git a/api/src/paths/project/{projectId}/funding-sources/add.ts b/api/src/paths/project/{projectId}/funding-sources/add.ts index b161c173f4..342331ea6f 100644 --- a/api/src/paths/project/{projectId}/funding-sources/add.ts +++ b/api/src/paths/project/{projectId}/funding-sources/add.ts @@ -6,6 +6,7 @@ import { HTTP400 } from '../../../../errors/custom-error'; import { PostFundingSource } from '../../../../models/project-create'; import { queries } from '../../../../queries/queries'; import { authorizeRequestHandler } from '../../../../request-handlers/security/authorization'; +import { PlatformService } from '../../../../services/platform-service'; import { getLogger } from '../../../../utils/logger'; import { addFundingSourceApiDocObject } from '../../../../utils/shared-api-docs'; @@ -30,14 +31,9 @@ POST.apiDoc = addFundingSourceApiDocObject('Add a funding source of a project.', export function addFundingSource(): RequestHandler { return async (req, res) => { - defaultLog.debug({ - label: 'Add project funding source', - message: 'params and body', - 'req.params': req.params, - 'req.body': req.body - }); - - if (!req.params.projectId) { + const projectId = Number(req.params.projectId); + + if (!projectId) { throw new HTTP400('Missing required path param `projectId`'); } @@ -54,7 +50,7 @@ export function addFundingSource(): RequestHandler { const addFundingSourceSQLStatement = queries.project.postProjectFundingSourceSQL( sanitizedPostFundingSource, - Number(req.params.projectId) + projectId ); if (!addFundingSourceSQLStatement) { @@ -69,6 +65,14 @@ export function addFundingSource(): RequestHandler { throw new HTTP400('Failed to insert project funding source data'); } + try { + const platformService = new PlatformService(connection); + await platformService.submitDwCAMetadataPackage(projectId); + } catch (error) { + // Don't fail the rest of the endpoint if submitting metadata fails + defaultLog.error({ label: 'addFundingSource->submitDwCAMetadataPackage', message: 'error', error }); + } + await connection.commit(); return res.status(200).json({ id: result.id }); diff --git a/api/src/paths/project/{projectId}/funding-sources/{pfsId}/delete.test.ts b/api/src/paths/project/{projectId}/funding-sources/{pfsId}/delete.test.ts index 27a5d92a72..bec135ad06 100644 --- a/api/src/paths/project/{projectId}/funding-sources/{pfsId}/delete.test.ts +++ b/api/src/paths/project/{projectId}/funding-sources/{pfsId}/delete.test.ts @@ -7,6 +7,7 @@ import * as db from '../../../../../database/db'; import { HTTPError } from '../../../../../errors/custom-error'; import project_queries from '../../../../../queries/project'; import survey_queries from '../../../../../queries/survey'; +import { PlatformService } from '../../../../../services/platform-service'; import { getMockDBConnection } from '../../../../../__mocks__/db'; import * as deleteFundingSource from './delete'; @@ -134,6 +135,8 @@ describe('delete a funding source', () => { sinon.stub(survey_queries, 'deleteSurveyFundingSourceByProjectFundingSourceIdSQL').returns(SQL`some`); sinon.stub(project_queries, 'deleteProjectFundingSourceSQL').returns(SQL`something`); + sinon.stub(PlatformService.prototype, 'submitDwCAMetadataPackage').resolves(); + const result = deleteFundingSource.deleteFundingSource(); await result(sampleReq, sampleRes as any, (null as unknown) as any); diff --git a/api/src/paths/project/{projectId}/funding-sources/{pfsId}/delete.ts b/api/src/paths/project/{projectId}/funding-sources/{pfsId}/delete.ts index f5494918dd..9c1f03e5b5 100644 --- a/api/src/paths/project/{projectId}/funding-sources/{pfsId}/delete.ts +++ b/api/src/paths/project/{projectId}/funding-sources/{pfsId}/delete.ts @@ -5,6 +5,7 @@ import { getDBConnection } from '../../../../../database/db'; import { HTTP400 } from '../../../../../errors/custom-error'; import { queries } from '../../../../../queries/queries'; import { authorizeRequestHandler } from '../../../../../request-handlers/security/authorization'; +import { PlatformService } from '../../../../../services/platform-service'; import { getLogger } from '../../../../../utils/logger'; import { deleteFundingSourceApiDocObject } from '../../../../../utils/shared-api-docs'; @@ -32,13 +33,14 @@ DELETE.apiDoc = deleteFundingSourceApiDocObject( export function deleteFundingSource(): RequestHandler { return async (req, res) => { - defaultLog.debug({ label: 'Delete project funding source', message: 'params', req_params: req.params }); + const projectId = Number(req.params.projectId); + const pfsId = Number(req.params.pfsId); - if (!req.params.projectId) { + if (!projectId) { throw new HTTP400('Missing required path param `projectId`'); } - if (!req.params.pfsId) { + if (!pfsId) { throw new HTTP400('Missing required path param `pfsId`'); } @@ -48,13 +50,10 @@ export function deleteFundingSource(): RequestHandler { await connection.open(); const surveyFundingSourceDeleteStatement = queries.survey.deleteSurveyFundingSourceByProjectFundingSourceIdSQL( - Number(req.params.pfsId) + pfsId ); - const deleteProjectFundingSourceSQLStatement = queries.project.deleteProjectFundingSourceSQL( - Number(req.params.projectId), - Number(req.params.pfsId) - ); + const deleteProjectFundingSourceSQLStatement = queries.project.deleteProjectFundingSourceSQL(projectId, pfsId); if (!deleteProjectFundingSourceSQLStatement || !surveyFundingSourceDeleteStatement) { throw new HTTP400('Failed to build SQL delete statement'); @@ -71,6 +70,14 @@ export function deleteFundingSource(): RequestHandler { throw new HTTP400('Failed to delete project funding source'); } + try { + const platformService = new PlatformService(connection); + await platformService.submitDwCAMetadataPackage(projectId); + } catch (error) { + // Don't fail the rest of the endpoint if submitting metadata fails + defaultLog.error({ label: 'deleteFundingSource->submitDwCAMetadataPackage', message: 'error', error }); + } + await connection.commit(); return res.status(200).json(projectFundingSourceDeleteResponse && projectFundingSourceDeleteResponse.rowCount); diff --git a/api/src/paths/project/{projectId}/participants/create.ts b/api/src/paths/project/{projectId}/participants/create.ts index b06dd1d135..a550417721 100644 --- a/api/src/paths/project/{projectId}/participants/create.ts +++ b/api/src/paths/project/{projectId}/participants/create.ts @@ -101,7 +101,9 @@ POST.apiDoc = { export function createProjectParticipants(): RequestHandler { return async (req, res) => { - if (!req.params.projectId) { + const projectId = Number(req.params.projectId); + + if (!projectId) { throw new HTTP400('Missing required param `projectId`'); } @@ -112,8 +114,6 @@ export function createProjectParticipants(): RequestHandler { const connection = getDBConnection(req['keycloak_token']); try { - const projectId = Number(req.params.projectId); - const participants: { userIdentifier: string; identitySource: string; roleId: number }[] = req.body.participants; await connection.open(); diff --git a/api/src/paths/project/{projectId}/participants/{projectParticipationId}/delete.test.ts b/api/src/paths/project/{projectId}/participants/{projectParticipationId}/delete.test.ts index 585b609870..7ca7d9cc6e 100644 --- a/api/src/paths/project/{projectId}/participants/{projectParticipationId}/delete.test.ts +++ b/api/src/paths/project/{projectId}/participants/{projectParticipationId}/delete.test.ts @@ -10,6 +10,7 @@ import { ProjectService } from '../../../../../services/project-service'; import { getMockDBConnection, getRequestHandlerMocks } from '../../../../../__mocks__/db'; import * as doAllProjectsHaveAProjectLead from '../../../../user/{userId}/delete'; import * as delete_project_participant from './delete'; + chai.use(sinonChai); describe('Delete a project participant.', () => { diff --git a/api/src/paths/project/{projectId}/participants/{projectParticipationId}/delete.ts b/api/src/paths/project/{projectId}/participants/{projectParticipationId}/delete.ts index 9342241ba8..02d212961e 100644 --- a/api/src/paths/project/{projectId}/participants/{projectParticipationId}/delete.ts +++ b/api/src/paths/project/{projectId}/participants/{projectParticipationId}/delete.ts @@ -76,13 +76,14 @@ DELETE.apiDoc = { export function deleteProjectParticipant(): RequestHandler { return async (req, res) => { - defaultLog.debug({ label: 'deleteProjectParticipant', message: 'params', req_params: req.params }); + const projectId = Number(req.params.projectId); + const projectParticipationId = Number(req.params.projectParticipationId); - if (!req.params.projectId) { + if (!projectId) { throw new HTTP400('Missing required path param `projectId`'); } - if (!req.params.projectParticipationId) { + if (!projectParticipationId) { throw new HTTP400('Missing required path param `projectParticipationId`'); } @@ -94,10 +95,10 @@ export function deleteProjectParticipant(): RequestHandler { const projectService = new ProjectService(connection); // Check project lead roles before deleting user - const projectParticipantsResponse1 = await projectService.getProjectParticipants(Number(req.params.projectId)); + const projectParticipantsResponse1 = await projectService.getProjectParticipants(projectId); const projectHasLeadResponse1 = doAllProjectsHaveAProjectLead(projectParticipantsResponse1); - const result = await deleteProjectParticipationRecord(Number(req.params.projectParticipationId), connection); + const result = await deleteProjectParticipationRecord(projectParticipationId, connection); if (!result || !result.system_user_id) { // The delete result is missing necesary data, fail the request @@ -107,7 +108,7 @@ export function deleteProjectParticipant(): RequestHandler { // If Project Lead roles are invalide skip check to prevent removal of only Project Lead of project // (Project is already missing Project Lead and is in a bad state) if (projectHasLeadResponse1) { - const projectParticipantsResponse2 = await projectService.getProjectParticipants(Number(req.params.projectId)); + const projectParticipantsResponse2 = await projectService.getProjectParticipants(projectId); const projectHasLeadResponse2 = doAllProjectsHaveAProjectLead(projectParticipantsResponse2); if (!projectHasLeadResponse2) { diff --git a/api/src/paths/project/{projectId}/participants/{projectParticipationId}/update.ts b/api/src/paths/project/{projectId}/participants/{projectParticipationId}/update.ts index 115249a4df..32883f499e 100644 --- a/api/src/paths/project/{projectId}/participants/{projectParticipationId}/update.ts +++ b/api/src/paths/project/{projectId}/participants/{projectParticipationId}/update.ts @@ -91,17 +91,19 @@ PUT.apiDoc = { export function updateProjectParticipantRole(): RequestHandler { return async (req, res) => { - defaultLog.debug({ label: 'updateProjectParticipantRole', message: 'params', req_params: req.params }); + const projectId = Number(req.params.projectId); + const projectParticipationId = Number(req.params.projectParticipationId); + const roleId = Number(req.body.roleId); - if (!req.params.projectId) { + if (!projectId) { throw new HTTP400('Missing required path param `projectId`'); } - if (!req.params.projectParticipationId) { + if (!projectParticipationId) { throw new HTTP400('Missing required path param `projectParticipationId`'); } - if (!req.body.roleId) { + if (!roleId) { throw new HTTP400('Missing required body param `roleId`'); } @@ -117,7 +119,7 @@ export function updateProjectParticipantRole(): RequestHandler { const projectHasLeadResponse1 = doAllProjectsHaveAProjectLead(projectParticipantsResponse1); // Delete the user's old participation record, returning the old record - const result = await deleteProjectParticipationRecord(Number(req.params.projectParticipationId), connection); + const result = await deleteProjectParticipationRecord(projectParticipationId, connection); if (!result || !result.system_user_id) { // The delete result is missing necessary data, fail the request @@ -125,9 +127,9 @@ export function updateProjectParticipantRole(): RequestHandler { } await projectService.addProjectParticipant( - Number(req.params.projectId), + projectId, Number(result.system_user_id), // get the user's system id from the old participation record - Number(req.body.roleId) + roleId ); // If Project Lead roles are invalid skip check to prevent removal of only Project Lead of project diff --git a/api/src/paths/project/{projectId}/publish.test.ts b/api/src/paths/project/{projectId}/publish.test.ts deleted file mode 100644 index 8cbdd3dcb7..0000000000 --- a/api/src/paths/project/{projectId}/publish.test.ts +++ /dev/null @@ -1,141 +0,0 @@ -import chai, { expect } from 'chai'; -import { describe } from 'mocha'; -import { QueryResult } from 'pg'; -import sinon from 'sinon'; -import sinonChai from 'sinon-chai'; -import * as db from '../../../database/db'; -import { HTTPError } from '../../../errors/custom-error'; -import { ProjectService } from '../../../services/project-service'; -import { getMockDBConnection } from '../../../__mocks__/db'; -import * as publish from './publish'; - -chai.use(sinonChai); - -const dbConnectionObj = getMockDBConnection(); - -const sampleReq = { - keycloak_token: {}, - params: { - projectId: 1 - }, - body: { - publish: true - } -} as any; - -let actualResult = { - id: null -}; - -const sampleRes = { - status: () => { - return { - json: (result: any) => { - actualResult = result; - } - }; - } -}; - -describe('project/{projectId}/publish', () => { - afterEach(() => { - sinon.restore(); - }); - - it('should throw a 400 error when missing request param projectId', async () => { - sinon.stub(db, 'getDBConnection').returns({ - ...dbConnectionObj, - systemUserId: () => { - return 20; - } - }); - - try { - const result = publish.publishProject(); - - await result( - { ...sampleReq, body: { ...sampleReq.body }, params: { projectId: undefined } }, - (null as unknown) as any, - (null as unknown) as any - ); - expect.fail(); - } catch (actualError) { - expect((actualError as HTTPError).status).to.equal(400); - expect((actualError as HTTPError).message).to.equal('Missing required path parameter: projectId'); - } - }); - - it('should throw a 400 error when missing request body', async () => { - sinon.stub(db, 'getDBConnection').returns({ - ...dbConnectionObj, - systemUserId: () => { - return 20; - } - }); - - try { - const result = publish.publishProject(); - - await result( - { ...sampleReq, body: (null as unknown) as any }, - (null as unknown) as any, - (null as unknown) as any - ); - expect.fail(); - } catch (actualError) { - expect((actualError as HTTPError).status).to.equal(400); - expect((actualError as HTTPError).message).to.equal('Missing request body'); - } - }); - - it('should throw a 400 error when missing publish flag in request body', async () => { - sinon.stub(db, 'getDBConnection').returns({ - ...dbConnectionObj, - systemUserId: () => { - return 20; - } - }); - - try { - const result = publish.publishProject(); - - await result( - { ...sampleReq, body: { ...sampleReq.body, publish: undefined } }, - (null as unknown) as any, - (null as unknown) as any - ); - expect.fail(); - } catch (actualError) { - expect((actualError as HTTPError).status).to.equal(400); - expect((actualError as HTTPError).message).to.equal('Missing publish flag in request body'); - } - }); - - it('should return the project id on success', async () => { - sinon.stub(db, 'getDBConnection').returns({ - ...dbConnectionObj, - systemUserId: () => { - return 20; - }, - query: async () => { - return { - rowCount: 1, - rows: [ - { - id: 1, - create_date: '2020/04/04' - } - ] - } as QueryResult; - } - }); - - sinon.stub(ProjectService.prototype, 'updatePublishStatus').resolves(1); - - const result = publish.publishProject(); - - await result(sampleReq, sampleRes as any, (null as unknown) as any); - - expect(actualResult.id).to.equal(1); - }); -}); diff --git a/api/src/paths/project/{projectId}/publish.ts b/api/src/paths/project/{projectId}/publish.ts deleted file mode 100644 index 1ae11be4e1..0000000000 --- a/api/src/paths/project/{projectId}/publish.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { RequestHandler } from 'express'; -import { Operation } from 'express-openapi'; -import { PROJECT_ROLE } from '../../../constants/roles'; -import { getDBConnection } from '../../../database/db'; -import { HTTP400 } from '../../../errors/custom-error'; -import { projectIdResponseObject } from '../../../openapi/schemas/project'; -import { authorizeRequestHandler } from '../../../request-handlers/security/authorization'; -import { ProjectService } from '../../../services/project-service'; -import { getLogger } from '../../../utils/logger'; - -const defaultLog = getLogger('paths/project/{projectId}/publish'); - -export const PUT: Operation = [ - authorizeRequestHandler((req) => { - return { - and: [ - { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD], - projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' - } - ] - }; - }), - publishProject() -]; - -PUT.apiDoc = { - description: 'Publish or unpublish a project.', - tags: ['project'], - security: [ - { - Bearer: [] - } - ], - parameters: [ - { - in: 'path', - name: 'projectId', - schema: { - type: 'number' - }, - required: true - } - ], - requestBody: { - description: 'Publish or unpublish put request object.', - content: { - 'application/json': { - schema: { - title: 'Publish request object', - type: 'object', - required: ['publish'], - properties: { - publish: { - title: 'publish?', - type: 'boolean' - } - } - } - } - } - }, - responses: { - 200: { - description: 'Project publish request completed successfully.', - content: { - 'application/json': { - schema: { - // TODO is there any return value? or is it just an HTTP status with no content? - ...(projectIdResponseObject as object) - } - } - } - }, - 400: { - $ref: '#/components/responses/400' - }, - 401: { - $ref: '#/components/responses/401' - }, - 403: { - $ref: '#/components/responses/401' - }, - 500: { - $ref: '#/components/responses/500' - }, - default: { - $ref: '#/components/responses/default' - } - } -}; - -/** - * Update a project. - * - * @returns {RequestHandler} - */ -export function publishProject(): RequestHandler { - return async (req, res) => { - const connection = getDBConnection(req['keycloak_token']); - - try { - const projectId = Number(req.params.projectId); - - if (!projectId) { - throw new HTTP400('Missing required path parameter: projectId'); - } - - if (!req.body) { - throw new HTTP400('Missing request body'); - } - - if (req.body.publish === undefined) { - throw new HTTP400('Missing publish flag in request body'); - } - - const publish: boolean = req.body.publish; - - await connection.open(); - - const projectService = new ProjectService(connection); - - const result = await projectService.updatePublishStatus(projectId, publish); - - await connection.commit(); - return res.status(200).json({ id: result }); - } catch (error) { - defaultLog.error({ label: 'publishProject', message: 'error', error }); - await connection.rollback(); - throw error; - } finally { - connection.release(); - } - }; -} diff --git a/api/src/paths/project/{projectId}/survey/create.test.ts b/api/src/paths/project/{projectId}/survey/create.test.ts index a9bc40e934..64a056796a 100644 --- a/api/src/paths/project/{projectId}/survey/create.test.ts +++ b/api/src/paths/project/{projectId}/survey/create.test.ts @@ -4,6 +4,7 @@ import sinon from 'sinon'; import sinonChai from 'sinon-chai'; import * as db from '../../../../database/db'; import { HTTPError } from '../../../../errors/custom-error'; +import { PlatformService } from '../../../../services/platform-service'; import { SurveyService } from '../../../../services/survey-service'; import { getMockDBConnection, getRequestHandlerMocks } from '../../../../__mocks__/db'; import { createSurvey } from './create'; @@ -23,6 +24,8 @@ describe('survey/create', () => { sinon.stub(SurveyService.prototype, 'createSurvey').resolves(2); + sinon.stub(PlatformService.prototype, 'submitDwCAMetadataPackage').resolves(); + const { mockReq, mockRes, mockNext } = getRequestHandlerMocks(); mockReq.params = { projectId: '1' }; diff --git a/api/src/paths/project/{projectId}/survey/create.ts b/api/src/paths/project/{projectId}/survey/create.ts index 750c952ddf..35c88ab982 100644 --- a/api/src/paths/project/{projectId}/survey/create.ts +++ b/api/src/paths/project/{projectId}/survey/create.ts @@ -5,6 +5,7 @@ import { getDBConnection } from '../../../../database/db'; import { PostSurveyObject } from '../../../../models/survey-create'; import { geoJsonFeature } from '../../../../openapi/schemas/geoJson'; import { authorizeRequestHandler } from '../../../../request-handlers/security/authorization'; +import { PlatformService } from '../../../../services/platform-service'; import { SurveyService } from '../../../../services/survey-service'; import { getLogger } from '../../../../utils/logger'; @@ -106,11 +107,20 @@ POST.apiDoc = { permit: { type: 'object', properties: { - permit_number: { - type: 'string' - }, - permit_type: { - type: 'string' + permits: { + type: 'array', + items: { + type: 'object', + required: ['permit_number', 'permit_type'], + properties: { + permit_number: { + type: 'string' + }, + permit_type: { + type: 'string' + } + } + } } } }, @@ -262,6 +272,14 @@ export function createSurvey(): RequestHandler { const surveyId = await surveyService.createSurvey(projectId, sanitizedPostSurveyData); + try { + const platformService = new PlatformService(connection); + await platformService.submitDwCAMetadataPackage(projectId); + } catch (error) { + // Don't fail the rest of the endpoint if submitting metadata fails + defaultLog.error({ label: 'createSurvey->submitDwCAMetadataPackage', message: 'error', error }); + } + await connection.commit(); return res.status(200).json({ id: surveyId }); diff --git a/api/src/paths/project/{projectId}/survey/permits/list.test.ts b/api/src/paths/project/{projectId}/survey/permits/list.test.ts deleted file mode 100644 index 702552b27a..0000000000 --- a/api/src/paths/project/{projectId}/survey/permits/list.test.ts +++ /dev/null @@ -1,147 +0,0 @@ -import chai, { expect } from 'chai'; -import { describe } from 'mocha'; -import sinon from 'sinon'; -import sinonChai from 'sinon-chai'; -import SQL from 'sql-template-strings'; -import * as db from '../../../../../database/db'; -import { HTTPError } from '../../../../../errors/custom-error'; -import survey_queries from '../../../../../queries/survey'; -import { getMockDBConnection } from '../../../../../__mocks__/db'; -import * as list from './list'; - -chai.use(sinonChai); - -describe('getSurveyPermits', () => { - afterEach(() => { - sinon.restore(); - }); - - const dbConnectionObj = getMockDBConnection(); - - const sampleReq = { - keycloak_token: {}, - params: { - projectId: 1 - } - } as any; - - let actualResult: any = null; - - const sampleRes = { - status: () => { - return { - json: (result: any) => { - actualResult = result; - } - }; - } - }; - - it('should throw a 400 error when no project id path param', async () => { - sinon.stub(db, 'getDBConnection').returns({ - ...dbConnectionObj, - systemUserId: () => { - return 20; - } - }); - - try { - const result = list.getSurveyPermits(); - - await result( - { ...sampleReq, params: { ...sampleReq.params, projectId: null } }, - (null as unknown) as any, - (null as unknown) as any - ); - expect.fail(); - } catch (actualError) { - expect((actualError as HTTPError).status).to.equal(400); - expect((actualError as HTTPError).message).to.equal('Missing required path param `projectId`'); - } - }); - - it('should throw a 400 error when no sql statement returned for assignable survey permits', async () => { - sinon.stub(db, 'getDBConnection').returns({ - ...dbConnectionObj, - systemUserId: () => { - return 20; - } - }); - - sinon.stub(survey_queries, 'getAllAssignablePermitsForASurveySQL').returns(null); - - try { - const result = list.getSurveyPermits(); - - await result(sampleReq, (null as unknown) as any, (null as unknown) as any); - expect.fail(); - } catch (actualError) { - expect((actualError as HTTPError).status).to.equal(400); - expect((actualError as HTTPError).message).to.equal('Failed to build SQL get statement'); - } - }); - - it('should return the survey permits on success', async () => { - const surveyPermits = [ - { - number: '123', - type: 'scientific' - }, - { - number: '12345', - type: 'wildlife' - } - ]; - - const mockQuery = sinon.stub(); - - mockQuery.resolves({ rows: surveyPermits }); - - sinon.stub(db, 'getDBConnection').returns({ - ...dbConnectionObj, - systemUserId: () => { - return 20; - }, - query: mockQuery - }); - - sinon.stub(survey_queries, 'getAllAssignablePermitsForASurveySQL').returns(SQL`some query`); - - const result = list.getSurveyPermits(); - - await result(sampleReq, sampleRes as any, (null as unknown) as any); - - expect(actualResult).to.eql([ - { - permit_number: '123', - permit_type: 'scientific' - }, - { - permit_number: '12345', - permit_type: 'wildlife' - } - ]); - }); - - it('should return an empty array when survey permits response has no rows', async () => { - const mockQuery = sinon.stub(); - - mockQuery.resolves({ rows: null }); - - sinon.stub(db, 'getDBConnection').returns({ - ...dbConnectionObj, - systemUserId: () => { - return 20; - }, - query: mockQuery - }); - - sinon.stub(survey_queries, 'getAllAssignablePermitsForASurveySQL').returns(SQL`some query`); - - const result = list.getSurveyPermits(); - - await result(sampleReq, sampleRes as any, (null as unknown) as any); - - expect(actualResult).to.be.eql([]); - }); -}); diff --git a/api/src/paths/project/{projectId}/survey/permits/list.ts b/api/src/paths/project/{projectId}/survey/permits/list.ts deleted file mode 100644 index 7ba5ebd9c8..0000000000 --- a/api/src/paths/project/{projectId}/survey/permits/list.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { RequestHandler } from 'express'; -import { Operation } from 'express-openapi'; -import { PROJECT_ROLE } from '../../../../../constants/roles'; -import { getDBConnection } from '../../../../../database/db'; -import { HTTP400 } from '../../../../../errors/custom-error'; -import { GetPermitData } from '../../../../../models/project-view'; -import { queries } from '../../../../../queries/queries'; -import { authorizeRequestHandler } from '../../../../../request-handlers/security/authorization'; -import { getLogger } from '../../../../../utils/logger'; - -const defaultLog = getLogger('/api/project/{projectId}/survey/permits/list'); - -export const GET: Operation = [ - authorizeRequestHandler((req) => { - return { - and: [ - { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD, PROJECT_ROLE.PROJECT_EDITOR, PROJECT_ROLE.PROJECT_VIEWER], - projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' - } - ] - }; - }), - getSurveyPermits() -]; - -GET.apiDoc = { - description: 'Fetches a list of permits for a survey based on a project.', - tags: ['permits'], - security: [ - { - Bearer: [] - } - ], - parameters: [ - { - in: 'path', - name: 'projectId', - schema: { - type: 'number' - }, - required: true - } - ], - responses: { - 200: { - description: 'Permits get response array.', - content: { - 'application/json': { - schema: { - type: 'array', - items: { - title: 'Survey permit Get Response Object', - type: 'object', - properties: { - permit_number: { - type: 'string' - }, - permit_type: { - type: 'string' - } - } - }, - description: 'Permits applicable for the survey' - } - } - } - }, - 401: { - $ref: '#/components/responses/401' - }, - default: { - $ref: '#/components/responses/default' - } - } -}; - -export function getSurveyPermits(): RequestHandler { - return async (req, res) => { - defaultLog.debug({ label: 'Get survey permits list', message: 'params', req_params: req.params }); - - if (!req.params.projectId) { - throw new HTTP400('Missing required path param `projectId`'); - } - - const connection = getDBConnection(req['keycloak_token']); - - try { - const getSurveyPermitsSQLStatement = queries.survey.getAllAssignablePermitsForASurveySQL( - Number(req.params.projectId) - ); - - if (!getSurveyPermitsSQLStatement) { - throw new HTTP400('Failed to build SQL get statement'); - } - - await connection.open(); - - const response = await connection.query(getSurveyPermitsSQLStatement.text, getSurveyPermitsSQLStatement.values); - - await connection.commit(); - - const result = (response && response.rows) || null; - - const getSurveyPermitsData = new GetPermitData(result); - - if (!getSurveyPermitsData) { - return res.status(200).json(null); - } - - return res.status(200).json(getSurveyPermitsData.permits); - } catch (error) { - defaultLog.error({ label: 'getSurveyPermits', message: 'error', error }); - await connection.rollback(); - throw error; - } finally { - connection.release(); - } - }; -} diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/{attachmentId}/metadata/get.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/{attachmentId}/metadata/get.ts index cec171ecf7..bc78e48dab 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/{attachmentId}/metadata/get.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/attachments/{attachmentId}/metadata/get.ts @@ -38,7 +38,8 @@ GET.apiDoc = { in: 'path', name: 'projectId', schema: { - type: 'number' + type: 'integer', + minimum: 1 }, required: true }, @@ -46,7 +47,8 @@ GET.apiDoc = { in: 'path', name: 'surveyId', schema: { - type: 'number' + type: 'integer', + minimum: 1 }, required: true }, @@ -54,7 +56,8 @@ GET.apiDoc = { in: 'path', name: 'attachmentId', schema: { - type: 'number' + type: 'integer', + minimum: 1 }, required: true } @@ -157,7 +160,7 @@ export function getSurveyReportMetaData(): RequestHandler { try { const getProjectReportAttachmentSQLStatement = queries.survey.getSurveyReportAttachmentSQL( - Number(req.params.projectId), + Number(req.params.surveyId), Number(req.params.attachmentId) ); diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/delete.test.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/delete.test.ts index ef6b94d9c8..978d429edc 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/delete.test.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/delete.test.ts @@ -7,6 +7,7 @@ import SQL from 'sql-template-strings'; import * as db from '../../../../../database/db'; import { HTTPError } from '../../../../../errors/custom-error'; import survey_queries from '../../../../../queries/survey'; +import { PlatformService } from '../../../../../services/platform-service'; import * as file_utils from '../../../../../utils/file-utils'; import { getMockDBConnection } from '../../../../../__mocks__/db'; import * as delete_survey from './delete'; @@ -146,6 +147,8 @@ describe('deleteSurvey', () => { sinon.stub(survey_queries, 'deleteSurveySQL').returns(SQL`something`); sinon.stub(file_utils, 'deleteFileFromS3').resolves('non null response' as DeleteObjectOutput); + sinon.stub(PlatformService.prototype, 'submitDwCAMetadataPackage').resolves(); + const result = delete_survey.deleteSurvey(); await result(sampleReq, sampleRes as any, (null as unknown) as any); diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/delete.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/delete.ts index 634bd34b34..56ec331587 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/delete.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/delete.ts @@ -5,6 +5,7 @@ import { getDBConnection, IDBConnection } from '../../../../../database/db'; import { HTTP400 } from '../../../../../errors/custom-error'; import { queries } from '../../../../../queries/queries'; import { authorizeRequestHandler } from '../../../../../request-handlers/security/authorization'; +import { PlatformService } from '../../../../../services/platform-service'; import { deleteFileFromS3 } from '../../../../../utils/file-utils'; import { getLogger } from '../../../../../utils/logger'; @@ -76,9 +77,10 @@ DELETE.apiDoc = { export function deleteSurvey(): RequestHandler { return async (req, res) => { - defaultLog.debug({ label: 'Delete survey', message: 'params', req_params: req.params }); + const projectId = Number(req.params.projectId); + const surveyId = Number(req.params.surveyId); - if (!req.params.surveyId) { + if (!surveyId) { throw new HTTP400('Missing required path param `surveyId`'); } @@ -91,13 +93,13 @@ export function deleteSurvey(): RequestHandler { * Get the attachment S3 keys for all attachments associated to this survey * Used to delete them from S3 separately later */ - const surveyAttachmentS3Keys: string[] = await getSurveyAttachmentS3Keys(Number(req.params.surveyId), connection); + const surveyAttachmentS3Keys: string[] = await getSurveyAttachmentS3Keys(surveyId, connection); /** * PART 2 * Delete the survey and all associated records/resources from our DB */ - const deleteSurveySQLStatement = queries.survey.deleteSurveySQL(Number(req.params.surveyId)); + const deleteSurveySQLStatement = queries.survey.deleteSurveySQL(surveyId); await connection.query(deleteSurveySQLStatement.text, deleteSurveySQLStatement.values); @@ -111,6 +113,14 @@ export function deleteSurvey(): RequestHandler { return res.status(200).json(null); } + try { + const platformService = new PlatformService(connection); + await platformService.submitDwCAMetadataPackage(projectId); + } catch (error) { + // Don't fail the rest of the endpoint if submitting metadata fails + defaultLog.error({ label: 'deleteSurvey->submitDwCAMetadataPackage', message: 'error', error }); + } + await connection.commit(); return res.status(200).json(true); diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/observation/submission/get.test.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/observation/submission/get.test.ts index 8e967cdf57..edb960348d 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/observation/submission/get.test.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/observation/submission/get.test.ts @@ -56,29 +56,6 @@ describe('getObservationSubmission', () => { } }); - it('should throw a 400 error when no sql statement returned for getLatestSurveyOccurrenceSubmission', async () => { - sinon.stub(db, 'getDBConnection').returns({ - ...dbConnectionObj, - systemUserId: () => { - return 20; - } - }); - - sinon.stub(survey_queries, 'getLatestSurveyOccurrenceSubmissionSQL').returns(null); - - try { - const result = observationSubmission.getOccurrenceSubmission(); - - await result(sampleReq, (null as unknown) as any, (null as unknown) as any); - expect.fail(); - } catch (actualError) { - expect((actualError as HTTPError).status).to.equal(400); - expect((actualError as HTTPError).message).to.equal( - 'Failed to build SQL getLatestSurveyOccurrenceSubmissionSQL statement' - ); - } - }); - it('should return an observation submission, on success with no rejected files', async () => { const mockQuery = sinon.stub(); diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/publish.test.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/publish.test.ts deleted file mode 100644 index e7b6ade228..0000000000 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/publish.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import chai, { expect } from 'chai'; -import { describe } from 'mocha'; -import sinon from 'sinon'; -import sinonChai from 'sinon-chai'; -import * as db from '../../../../../database/db'; -import { HTTPError } from '../../../../../errors/custom-error'; -import { SurveyService } from '../../../../../services/survey-service'; -import { getMockDBConnection, getRequestHandlerMocks } from '../../../../../__mocks__/db'; -import { publishSurvey } from './publish'; - -chai.use(sinonChai); - -describe('publishSurvey', () => { - afterEach(() => { - sinon.restore(); - }); - - it('publishes a survey', async () => { - const dbConnectionObj = getMockDBConnection(); - - sinon.stub(db, 'getDBConnection').returns(dbConnectionObj); - - sinon.stub(SurveyService.prototype, 'publishSurvey').resolves(); - - const { mockReq, mockRes, mockNext } = getRequestHandlerMocks(); - - mockReq.params = { - projectId: '1', - surveyId: '2' - }; - - mockReq.body = { - publish: true - }; - - try { - const requestHandler = publishSurvey(); - - await requestHandler(mockReq, mockRes, mockNext); - } catch (actualError) { - expect.fail(); - } - - expect(mockRes.statusValue).to.equal(200); - }); - - it('catches and re-throws error', async () => { - const dbConnectionObj = getMockDBConnection({ release: sinon.stub() }); - - sinon.stub(db, 'getDBConnection').returns(dbConnectionObj); - - sinon.stub(SurveyService.prototype, 'publishSurvey').rejects(new Error('a test error')); - - const { mockReq, mockRes, mockNext } = getRequestHandlerMocks(); - - try { - const requestHandler = publishSurvey(); - - await requestHandler(mockReq, mockRes, mockNext); - expect.fail(); - } catch (actualError) { - expect(dbConnectionObj.release).to.have.been.called; - - expect((actualError as HTTPError).message).to.equal('a test error'); - } - }); -}); diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/publish.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/publish.ts deleted file mode 100644 index db3f964610..0000000000 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/publish.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { RequestHandler } from 'express'; -import { Operation } from 'express-openapi'; -import { PROJECT_ROLE } from '../../../../../constants/roles'; -import { getDBConnection } from '../../../../../database/db'; -import { authorizeRequestHandler } from '../../../../../request-handlers/security/authorization'; -import { SurveyService } from '../../../../../services/survey-service'; -import { getLogger } from '../../../../../utils/logger'; - -const defaultLog = getLogger('paths/project/{projectId}/survey/{surveyId}/publish'); - -export const PUT: Operation = [ - authorizeRequestHandler((req) => { - return { - and: [ - { - validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD], - projectId: Number(req.params.projectId), - discriminator: 'ProjectRole' - } - ] - }; - }), - publishSurvey() -]; - -PUT.apiDoc = { - description: 'Publish or unpublish a survey.', - tags: ['survey'], - security: [ - { - Bearer: [] - } - ], - parameters: [ - { - in: 'path', - name: 'projectId', - schema: { - type: 'integer', - minimum: 1 - }, - required: true - }, - { - in: 'path', - name: 'surveyId', - schema: { - type: 'integer', - minimum: 1 - }, - required: true - } - ], - requestBody: { - description: 'Publish or unpublish put request object.', - content: { - 'application/json': { - schema: { - title: 'Publish request object', - type: 'object', - required: ['publish'], - properties: { - publish: { - description: 'Set to `true` to publish the survey, `false` to unpublish the survey', - type: 'boolean' - } - } - } - } - } - }, - responses: { - 200: { - description: 'Survey publish request completed successfully.', - content: { - 'application/json': { - schema: { - // TODO is there any return value? or is it just an HTTP status with no content? - title: 'Survey Response Object', - type: 'object', - required: ['id'], - properties: { - id: { - type: 'number' - } - } - } - } - } - }, - 400: { - $ref: '#/components/responses/400' - }, - 401: { - $ref: '#/components/responses/401' - }, - 403: { - $ref: '#/components/responses/401' - }, - 500: { - $ref: '#/components/responses/500' - }, - default: { - $ref: '#/components/responses/default' - } - } -}; - -/** - * Publish survey. - * - * @returns {RequestHandler} - */ -export function publishSurvey(): RequestHandler { - return async (req, res) => { - const surveyId = Number(req.params.surveyId); - const publish: boolean = req.body.publish; - - const connection = getDBConnection(req['keycloak_token']); - try { - await connection.open(); - - const surveyService = new SurveyService(connection); - - await surveyService.publishSurvey(surveyId, publish); - - await connection.commit(); - - return res.status(200).send(); - } catch (error) { - defaultLog.error({ label: 'publishSurvey', message: 'error', error }); - await connection.rollback(); - throw error; - } finally { - connection.release(); - } - }; -} diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/summary/submission/upload.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/summary/submission/upload.ts index 858d3c511f..a925fd9ac2 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/summary/submission/upload.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/summary/submission/upload.ts @@ -3,7 +3,6 @@ import { Operation } from 'express-openapi'; import { PROJECT_ROLE } from '../../../../../../../constants/roles'; import { getDBConnection, IDBConnection } from '../../../../../../../database/db'; import { HTTP400 } from '../../../../../../../errors/custom-error'; -import { PostSummaryDetails } from '../../../../../../../models/summaryresults-create'; import { generateHeaderErrorMessage, generateRowErrorMessage } from '../../../../../../../paths/dwc/validate'; import { validateXLSX } from '../../../../../../../paths/xlsx/validate'; import { queries } from '../../../../../../../queries/queries'; @@ -36,7 +35,6 @@ export const POST: Operation = [ getValidationRules(), validateXLSX(), persistSummaryValidationResults(), - parseAndUploadSummarySubmissionInput(), returnSummarySubmissionId() ]; @@ -112,21 +110,28 @@ POST.apiDoc = { }; export enum SUMMARY_CLASS { - STUDY_AREA = 'survey area', - SUMMARY_STATISTIC = 'statistic', + STUDY_AREA = 'study area', + POPULATION_UNIT = 'population unit', + BLOCK_SAMPLE_UNIT_ID = 'block/sample unit', + PARAMETER = 'parameter', STRATUM = 'stratum', OBSERVED = 'observed', - ESTIMATE = 'estimate', - STANDARD_ERROR = 'se', - COEFFICIENT_VARIATION = 'cv', - CONFIDENCE_LEVEL = 'conf.level', - LOWER_CONFIDENCE_LIMIT = 'lcl', - UPPER_CONFIDENCE_LIMIT = 'ucl', - SIGHTABILITY_MODEL = 'sightability.model', - AREA = 'area', - AREA_FLOWN = 'area.flown', - OUTLIER_BLOCKS_REMOVED = 'outlier.blocks.removed', - ANALYSIS_METHOD = 'analysis.method' + ESTIMATED = 'estimated', + SIGHTABILITY_MODEL = 'sightability model', + SIGHTABILITY_CORRECTION_FACTOR = 'sightability correction factor', + SE = 'se', + COEFFICIENT_VARIATION = 'coefficient of variation (%)', + CONFIDENCE_LEVEL = 'confidence level (%)', + LOWER_CONFIDENCE_LEVEL = 'lower cl', + UPPER_CONFIDENCE_LEVEL = 'upper cl', + TOTAL_SURVEY_AREA = 'total survey area (km2)', + AREA_FLOWN = 'area flown (km2)', + TOTAL_KILOMETERS_SURVEYED = 'total kilometers surveyed (km)', + BEST_PARAMETER_VALUE_FLAG = 'best parameter value flag', + OUTLIER_BLOCKS_REMOVED = 'outlier blocks removed', + TOTAL_MARKED_ANIMALS_OBSERVED = 'total marked animals observed', + MARKER_ANIMALS_AVAILABLE = 'marked animals available', + PARAMETER_COMMENTS = 'parameter comments' } /** @@ -368,7 +373,19 @@ export function getValidationRules(): RequestHandler { ] }, { - name: 'Estimate', + name: 'Estimated', + description: '', + validations: [ + { + column_numeric_validator: { + name: '', + description: '' + } + } + ] + }, + { + name: 'Sightability Correction Factor', description: '', validations: [ { @@ -392,7 +409,7 @@ export function getValidationRules(): RequestHandler { ] }, { - name: 'CV', + name: 'Coefficient of Variation (%)', description: '', validations: [ { @@ -404,7 +421,7 @@ export function getValidationRules(): RequestHandler { ] }, { - name: 'Conf.Level', + name: 'Confidence Level (%)', description: '', validations: [ { @@ -416,7 +433,7 @@ export function getValidationRules(): RequestHandler { ] }, { - name: 'LCL', + name: 'Area Flown (km2)', description: '', validations: [ { @@ -428,7 +445,7 @@ export function getValidationRules(): RequestHandler { ] }, { - name: 'UCL', + name: 'Total Survey Area (km2)', description: '', validations: [ { @@ -440,7 +457,37 @@ export function getValidationRules(): RequestHandler { ] }, { - name: 'Area', + name: 'Total Kilometers Surveyed (km)', + description: '', + validations: [ + { + column_numeric_validator: { + name: '', + description: '' + } + } + ] + }, + { + name: 'Best Parameter Value Flag', + description: '', + validations: [ + { + column_code_validator: { + name: '', + description: '', + allowed_code_values: [ + { name: 'Yes', description: '' }, + { name: 'No', description: '' }, + { name: 'Unknown', description: '' }, + { name: 'Not Evaluated', description: '' } + ] + } + } + ] + }, + { + name: 'Total Marked Animals Observed', description: '', validations: [ { @@ -452,7 +499,7 @@ export function getValidationRules(): RequestHandler { ] }, { - name: 'Area.Flown', + name: 'Marked Animals Available', description: '', validations: [ { @@ -471,21 +518,28 @@ export function getValidationRules(): RequestHandler { { file_required_columns_validator: { required_columns: [ - 'Survey Area', - 'Statistic', + 'Study Area', + 'Population Unit', + 'Block/Sample Unit', + 'Parameter', 'Stratum', 'Observed', - 'Estimate', + 'Estimated', + 'Sightability Model', + 'Sightability Correction Factor', 'SE', - 'CV', - 'Conf.Level', - 'LCL', - 'UCL', - 'Sightability.Model', - 'Area', - 'Area.Flown', - 'Outlier.Blocks.Removed', - 'Analysis.Method' + 'Coefficient of Variation (%)', + 'Confidence Level (%)', + 'Lower CL', + 'Upper CL', + 'Total Survey Area (km2)', + 'Area Flown (km2)', + 'Total Kilometers Surveyed (km)', + 'Best Parameter Value Flag', + 'Outlier Blocks Removed', + 'Total Marked Animals Observed', + 'Marked Animals Available', + 'Parameter Comments' ] } } @@ -579,98 +633,6 @@ export function persistSummaryValidationResults(): RequestHandler { }; } -export function parseAndUploadSummarySubmissionInput(): RequestHandler { - return async (req, res, next) => { - const xlsxCsv: XLSXCSV = req['xlsx']; - - const summarySubmissionId = req['summarySubmissionId']; - - const connection = getDBConnection(req['keycloak_token']); - - const worksheets = xlsxCsv.workbook.worksheets; - - try { - await connection.open(); - - const promises: Promise[] = []; - - for (const worksheet of Object.values(worksheets)) { - const rowObjects = worksheet.getRowObjects(); - - for (const rowObject of Object.values(rowObjects)) { - const summaryObject = new PostSummaryDetails(); - - for (const columnName in rowObject) { - const columnValue = rowObject[columnName]; - - switch (columnName.toLowerCase()) { - case SUMMARY_CLASS.STUDY_AREA: - summaryObject.study_area_id = columnValue; - break; - case SUMMARY_CLASS.SUMMARY_STATISTIC: - summaryObject.parameter = columnValue; - break; - case SUMMARY_CLASS.STRATUM: - summaryObject.stratum = columnValue; - break; - case SUMMARY_CLASS.OBSERVED: - summaryObject.parameter_value = columnValue; - break; - case SUMMARY_CLASS.ESTIMATE: - summaryObject.parameter_estimate = columnValue; - break; - case SUMMARY_CLASS.STANDARD_ERROR: - summaryObject.standard_error = columnValue; - break; - case SUMMARY_CLASS.COEFFICIENT_VARIATION: - summaryObject.coefficient_variation = columnValue; - break; - case SUMMARY_CLASS.CONFIDENCE_LEVEL: - summaryObject.confidence_level_percent = columnValue; - break; - case SUMMARY_CLASS.UPPER_CONFIDENCE_LIMIT: - summaryObject.confidence_limit_upper = columnValue; - break; - case SUMMARY_CLASS.LOWER_CONFIDENCE_LIMIT: - summaryObject.confidence_limit_lower = columnValue; - break; - case SUMMARY_CLASS.SIGHTABILITY_MODEL: - summaryObject.sightability_model = columnValue; - break; - case SUMMARY_CLASS.AREA: - summaryObject.total_area_survey_sqm = columnValue; - break; - case SUMMARY_CLASS.AREA_FLOWN: - summaryObject.kilometres_surveyed = columnValue; - break; - case SUMMARY_CLASS.OUTLIER_BLOCKS_REMOVED: - summaryObject.outlier_blocks_removed = columnValue; - break; - case SUMMARY_CLASS.ANALYSIS_METHOD: - summaryObject.analysis_method = columnValue; - break; - default: - break; - } - } - promises.push(uploadScrapedSummarySubmission(summarySubmissionId, summaryObject, connection)); - } - } - - await Promise.all(promises); - - await connection.commit(); - next(); - } catch (error) { - defaultLog.error({ label: 'parseAndUploadSummaryDetails', message: 'error', error }); - await connection.rollback(); - throw error; - } finally { - connection.release(); - } - }; -} - function returnSummarySubmissionId(): RequestHandler { return async (req, res) => { const summarySubmissionId = req['summarySubmissionId']; diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/update.test.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/update.test.ts index 9fa1b40409..2a6f78ccaf 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/update.test.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/update.test.ts @@ -4,6 +4,7 @@ import sinon from 'sinon'; import sinonChai from 'sinon-chai'; import * as db from '../../../../../database/db'; import { HTTPError } from '../../../../../errors/custom-error'; +import { PlatformService } from '../../../../../services/platform-service'; import { SurveyService } from '../../../../../services/survey-service'; import { getMockDBConnection, getRequestHandlerMocks } from '../../../../../__mocks__/db'; import { updateSurvey } from './update'; @@ -22,6 +23,8 @@ describe('updateSurvey', () => { sinon.stub(SurveyService.prototype, 'updateSurvey').resolves(); + sinon.stub(PlatformService.prototype, 'submitDwCAMetadataPackage').resolves(); + const { mockReq, mockRes, mockNext } = getRequestHandlerMocks(); mockReq.params = { diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/update.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/update.ts index f65cae4e8c..dd4a9caad4 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/update.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/update.ts @@ -5,6 +5,7 @@ import { getDBConnection } from '../../../../../database/db'; import { PutSurveyObject } from '../../../../../models/survey-update'; import { geoJsonFeature } from '../../../../../openapi/schemas/geoJson'; import { authorizeRequestHandler } from '../../../../../request-handlers/security/authorization'; +import { PlatformService } from '../../../../../services/platform-service'; import { SurveyService } from '../../../../../services/survey-service'; import { getLogger } from '../../../../../utils/logger'; @@ -116,13 +117,25 @@ PUT.apiDoc = { }, permit: { type: 'object', - required: ['permit_number', 'permit_type'], properties: { - permit_number: { - type: 'string' - }, - permit_type: { - type: 'string' + permits: { + type: 'array', + items: { + type: 'object', + required: ['permit_number', 'permit_type'], + properties: { + permit_id: { + type: 'number', + nullable: true + }, + permit_number: { + type: 'string' + }, + permit_type: { + type: 'string' + } + } + } } } }, @@ -282,7 +295,15 @@ export function updateSurvey(): RequestHandler { const surveyService = new SurveyService(connection); - await surveyService.updateSurvey(projectId, surveyId, sanitizedPutSurveyData); + await surveyService.updateSurvey(surveyId, sanitizedPutSurveyData); + + try { + const platformService = new PlatformService(connection); + await platformService.submitDwCAMetadataPackage(projectId); + } catch (error) { + // Don't fail the rest of the endpoint if submitting metadata fails + defaultLog.error({ label: 'updateSurvey->submitDwCAMetadataPackage', message: 'error', error }); + } await connection.commit(); diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/upload.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/upload.ts new file mode 100644 index 0000000000..23df75fecc --- /dev/null +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/upload.ts @@ -0,0 +1,95 @@ +import { RequestHandler } from 'express'; +import { Operation } from 'express-openapi'; +import { SYSTEM_ROLE } from '../../../../../constants/roles'; +import { getDBConnection } from '../../../../../database/db'; +import { HTTP400 } from '../../../../../errors/custom-error'; +import { authorizeRequestHandler } from '../../../../../request-handlers/security/authorization'; +import { PlatformService } from '../../../../../services/platform-service'; +import { getLogger } from '../../../../../utils/logger'; + +const defaultLog = getLogger('/api/project/{projectId}/survey/{surveyId}/upload'); + +export const POST: Operation = [ + authorizeRequestHandler(() => { + return { + and: [ + { + validSystemRoles: [SYSTEM_ROLE.SYSTEM_ADMIN], + discriminator: 'SystemRole' + } + ] + }; + }), + uploadSurveyDataToBioHub() +]; + +POST.apiDoc = { + description: 'Upload survey/observation data to BioHub.', + tags: ['survey'], + security: [ + { + Bearer: [] + } + ], + parameters: [ + { + in: 'path', + name: 'projectId', + schema: { + type: 'integer', + minimum: 1 + }, + required: true + }, + { + in: 'path', + name: 'surveyId', + schema: { + type: 'integer', + minimum: 1 + }, + required: true + } + ], + responses: { + 200: { + description: 'Upload survey/observation data to BioHub OK.' + }, + 401: { + $ref: '#/components/responses/401' + }, + default: { + $ref: '#/components/responses/default' + } + } +}; + +export function uploadSurveyDataToBioHub(): RequestHandler { + return async (req, res) => { + const projectId = Number(req.params.projectId); + const surveyId = Number(req.params.surveyId); + + if (!surveyId) { + throw new HTTP400('Missing required path param `surveyId`'); + } + + const connection = getDBConnection(req['keycloak_token']); + + try { + await connection.open(); + + const platformService = new PlatformService(connection); + await platformService.uploadSurveyDataToBioHub(projectId, surveyId); + + await connection.commit(); + + return res.status(200).send(); + } catch (error) { + defaultLog.error({ label: 'uploadSurveyDataToBioHub', message: 'error', error }); + await connection.rollback(); + throw error; + } finally { + connection.release(); + } + }; +} diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/view.test.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/view.test.ts index d678d3bd8f..f289c785ce 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/view.test.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/view.test.ts @@ -26,7 +26,6 @@ describe('survey/{surveyId}/view', () => { end_date: '2020-05-05', biologist_first_name: 'first', biologist_last_name: 'last', - publish_date: '', revision_count: 1 }, species: { @@ -97,7 +96,6 @@ describe('survey/{surveyId}/view', () => { end_date: '2020-05-05', biologist_first_name: 'first', biologist_last_name: 'last', - publish_date: null, revision_count: 1 }, species: { diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/view.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/view.ts index e2002768eb..68e37bc1b5 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/view.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/view.ts @@ -82,7 +82,6 @@ GET.apiDoc = { 'start_date', 'biologist_first_name', 'biologist_last_name', - 'publish_date', 'revision_count' ], properties: { @@ -104,11 +103,6 @@ GET.apiDoc = { biologist_last_name: { type: 'string' }, - publish_date: { - oneOf: [{ type: 'object' }, { type: 'string', format: 'date' }], - nullable: true, - description: 'Determines if the record has been published' - }, revision_count: { type: 'number' } @@ -150,15 +144,25 @@ GET.apiDoc = { permit: { description: 'Survey Permit', type: 'object', - required: ['permit_number', 'permit_type'], properties: { - permit_number: { - type: 'string', - nullable: true - }, - permit_type: { - type: 'string', - nullable: true + permits: { + type: 'array', + items: { + type: 'object', + required: ['permit_id', 'permit_number', 'permit_type'], + properties: { + permit_id: { + type: 'number', + minimum: 1 + }, + permit_number: { + type: 'string' + }, + permit_type: { + type: 'string' + } + } + } } } }, diff --git a/api/src/paths/project/{projectId}/surveys.ts b/api/src/paths/project/{projectId}/surveys.ts index 45ac18b757..31a57dc0b6 100644 --- a/api/src/paths/project/{projectId}/surveys.ts +++ b/api/src/paths/project/{projectId}/surveys.ts @@ -64,7 +64,6 @@ GET.apiDoc = { 'biologist_last_name', 'start_date', 'geometry', - 'publish_date', 'survey_area_name', 'survey_name', 'revision_count' @@ -95,11 +94,6 @@ GET.apiDoc = { ...(geoJsonFeature as object) } }, - publish_date: { - oneOf: [{ type: 'object' }, { type: 'string', format: 'date' }], - nullable: true, - description: 'Determines if the record has been published' - }, survey_area_name: { type: 'string' }, @@ -145,17 +139,27 @@ GET.apiDoc = { } }, permit: { - description: 'Survey Permit', type: 'object', - required: ['permit_number', 'permit_type'], + description: 'Survey Permit Information', properties: { - permit_number: { - type: 'string', - nullable: true - }, - permit_type: { - type: 'string', - nullable: true + permits: { + description: 'Survey Permits', + type: 'array', + items: { + required: ['permit_id', 'permit_number', 'permit_type'], + properties: { + permit_id: { + type: 'number', + minimum: 1 + }, + permit_number: { + type: 'string' + }, + permit_type: { + type: 'string' + } + } + } } } }, diff --git a/api/src/paths/project/{projectId}/update.test.ts b/api/src/paths/project/{projectId}/update.test.ts index 98767455ca..10ab88ebe5 100644 --- a/api/src/paths/project/{projectId}/update.test.ts +++ b/api/src/paths/project/{projectId}/update.test.ts @@ -4,7 +4,7 @@ import sinon from 'sinon'; import sinonChai from 'sinon-chai'; import * as db from '../../../database/db'; import { HTTPError } from '../../../errors/custom-error'; -import { GetPermitData } from '../../../models/project-view'; +import { PlatformService } from '../../../services/platform-service'; import { ProjectService } from '../../../services/project-service'; import { getMockDBConnection, getRequestHandlerMocks } from '../../../__mocks__/db'; import * as update from './update'; @@ -47,7 +47,6 @@ describe('update', () => { const sampleResponse = { id: 1, coordinator: undefined, - permit: new GetPermitData(), project: undefined, objectives: undefined, location: undefined, @@ -61,7 +60,7 @@ describe('update', () => { }; mockReq.query = { - entity: ['permit'] + entity: ['coordinator'] }; sinon.stub(db, 'getDBConnection').returns(dbConnectionObj); @@ -73,7 +72,7 @@ describe('update', () => { await requestHandler(mockReq, mockRes, mockNext); expect(mockRes.statusValue).to.equal(200); - expect(ProjectService.prototype.getProjectEntitiesById).called.calledWith(1, ['permit']); + expect(ProjectService.prototype.getProjectEntitiesById).called.calledWith(1, ['coordinator']); expect(mockRes.sendValue).to.equal(sampleResponse); }); }); @@ -145,12 +144,10 @@ describe('update', () => { start_date: '2022-02-02', end_date: '2022-02-30', objectives: 'my objectives', - publish_date: '2022-02-02', revision_count: 0 }, iucn: {}, contact: {}, - permit: {}, funding: {}, partnerships: {}, location: {} @@ -160,6 +157,8 @@ describe('update', () => { sinon.stub(ProjectService.prototype, 'updateProject').resolves(); + sinon.stub(PlatformService.prototype, 'submitDwCAMetadataPackage').resolves(); + const requestHandler = update.updateProject(); await requestHandler(mockReq, mockRes, mockNext); diff --git a/api/src/paths/project/{projectId}/update.ts b/api/src/paths/project/{projectId}/update.ts index fc6ae3ba0d..3aa5aad724 100644 --- a/api/src/paths/project/{projectId}/update.ts +++ b/api/src/paths/project/{projectId}/update.ts @@ -6,6 +6,7 @@ import { HTTP400 } from '../../../errors/custom-error'; import { geoJsonFeature } from '../../../openapi/schemas/geoJson'; import { projectIdResponseObject, projectUpdatePutRequestObject } from '../../../openapi/schemas/project'; import { authorizeRequestHandler } from '../../../request-handlers/security/authorization'; +import { PlatformService } from '../../../services/platform-service'; import { ProjectService } from '../../../services/project-service'; import { getLogger } from '../../../utils/logger'; @@ -28,7 +29,6 @@ export const GET: Operation = [ export enum GET_ENTITIES { coordinator = 'coordinator', - permit = 'permit', project = 'project', objectives = 'objectives', location = 'location', @@ -86,7 +86,6 @@ GET.apiDoc = { 'project_activities', 'start_date', 'end_date', - 'publish_date', 'revision_count' ], nullable: true, @@ -113,38 +112,11 @@ GET.apiDoc = { format: 'date', description: 'ISO 8601 date string for the project end date' }, - publish_date: { - description: 'Status of the project being published/unpublished', - format: 'date', - type: 'string' - }, revision_count: { type: 'number' } } }, - permit: { - type: 'object', - required: ['permits'], - nullable: true, - properties: { - permits: { - type: 'array', - items: { - title: 'Project permit', - type: 'object', - properties: { - permit_number: { - type: 'string' - }, - permit_type: { - type: 'string' - } - } - } - } - } - }, coordinator: { title: 'Project coordinator', type: 'object', @@ -430,7 +402,6 @@ PUT.apiDoc = { export interface IUpdateProject { coordinator: object | null; - permit: object | null; project: object | null; objectives: object | null; location: object | null; @@ -467,6 +438,14 @@ export function updateProject(): RequestHandler { await projectService.updateProject(projectId, entities); + try { + const platformService = new PlatformService(connection); + await platformService.submitDwCAMetadataPackage(projectId); + } catch (error) { + // Don't fail the rest of the endpoint if submitting metadata fails + defaultLog.error({ label: 'updateProject->submitDwCAMetadataPackage', message: 'error', error }); + } + await connection.commit(); return res.status(200).json({ id: projectId }); diff --git a/api/src/paths/project/{projectId}/view.ts b/api/src/paths/project/{projectId}/view.ts index 54ae6159fe..034afc7d3a 100644 --- a/api/src/paths/project/{projectId}/view.ts +++ b/api/src/paths/project/{projectId}/view.ts @@ -50,17 +50,7 @@ GET.apiDoc = { schema: { title: 'Project get response object, for view purposes', type: 'object', - required: [ - 'id', - 'project', - 'permit', - 'coordinator', - 'objectives', - 'location', - 'iucn', - 'funding', - 'partnerships' - ], + required: ['id', 'project', 'coordinator', 'objectives', 'location', 'iucn', 'funding', 'partnerships'], properties: { id: { description: 'Project id', @@ -76,8 +66,7 @@ GET.apiDoc = { 'start_date', 'end_date', 'comments', - 'completion_status', - 'publish_date' + 'completion_status' ], properties: { project_name: { @@ -109,32 +98,6 @@ GET.apiDoc = { completion_status: { description: 'Status of the project being active/completed', type: 'string' - }, - publish_date: { - description: 'Status of the project being published/unpublished', - format: 'date', - type: 'string' - } - } - }, - permit: { - type: 'object', - required: ['permits'], - properties: { - permits: { - type: 'array', - items: { - title: 'Project permit', - type: 'object', - properties: { - permit_number: { - type: 'string' - }, - permit_type: { - type: 'string' - } - } - } } } }, diff --git a/api/src/paths/public/project/list.ts b/api/src/paths/public/project/list.ts index 469b5d8815..e550a2cfb4 100644 --- a/api/src/paths/public/project/list.ts +++ b/api/src/paths/public/project/list.ts @@ -10,7 +10,7 @@ const defaultLog = getLogger('paths/public/projects'); export const GET: Operation = [getPublicProjectsList()]; GET.apiDoc = { - description: 'Gets a list of public facing (published) projects.', + description: 'Gets a list of public facing projects.', tags: ['public', 'projects'], responses: { 200: { @@ -42,7 +42,7 @@ GET.apiDoc = { }; /** - * Get all public facing (published) projects. + * Get all public facing projects. * * @returns {RequestHandler} */ diff --git a/api/src/paths/public/project/{projectId}/attachments/list.ts b/api/src/paths/public/project/{projectId}/attachments/list.ts index 34e101599d..ffd9ac3659 100644 --- a/api/src/paths/public/project/{projectId}/attachments/list.ts +++ b/api/src/paths/public/project/{projectId}/attachments/list.ts @@ -11,7 +11,7 @@ const defaultLog = getLogger('/api/public/project/{projectId}/attachments/list') export const GET: Operation = [getPublicProjectAttachments()]; GET.apiDoc = { - description: 'Fetches a list of attachments of a public (published) project.', + description: 'Fetches a list of attachments of a public project.', tags: ['attachments'], parameters: [ { diff --git a/api/src/paths/public/project/{projectId}/view.ts b/api/src/paths/public/project/{projectId}/view.ts index 7485a53408..3c351911e0 100644 --- a/api/src/paths/public/project/{projectId}/view.ts +++ b/api/src/paths/public/project/{projectId}/view.ts @@ -10,7 +10,7 @@ const defaultLog = getLogger('paths/public/project/{projectId}/view'); export const GET: Operation = [getPublicProjectForView()]; GET.apiDoc = { - description: 'Get a public (published) project, for view-only purposes.', + description: 'Get a public project, for view-only purposes.', tags: ['project'], parameters: [ { @@ -30,17 +30,7 @@ GET.apiDoc = { schema: { title: 'Project get response object, for view purposes', type: 'object', - required: [ - 'id', - 'project', - 'permit', - 'coordinator', - 'objectives', - 'location', - 'iucn', - 'funding', - 'partnerships' - ], + required: ['id', 'project', 'coordinator', 'objectives', 'location', 'iucn', 'funding', 'partnerships'], properties: { id: { description: 'Project id', @@ -56,8 +46,7 @@ GET.apiDoc = { 'start_date', 'end_date', 'comments', - 'completion_status', - 'publish_date' + 'completion_status' ], properties: { project_name: { @@ -89,32 +78,6 @@ GET.apiDoc = { completion_status: { description: 'Status of the project being active/completed', type: 'string' - }, - publish_date: { - description: 'Status of the project being published/unpublished', - format: 'date', - type: 'string' - } - } - }, - permit: { - type: 'object', - required: ['permits'], - properties: { - permits: { - type: 'array', - items: { - title: 'Project permit', - type: 'object', - properties: { - permit_number: { - type: 'string' - }, - permit_type: { - type: 'string' - } - } - } } } }, @@ -287,7 +250,7 @@ GET.apiDoc = { }; /** - * Get a public (published) project by its id. + * Get a public project by its id. * * @returns {RequestHandler} */ diff --git a/api/src/paths/public/search.ts b/api/src/paths/public/search.ts index 830e439b64..930cc23673 100644 --- a/api/src/paths/public/search.ts +++ b/api/src/paths/public/search.ts @@ -12,7 +12,7 @@ const defaultLog = getLogger('paths/public/search'); export const GET: Operation = [getSearchResults()]; GET.apiDoc = { - description: 'Gets a list of published project geometries for public view', + description: 'Gets a list of project geometries for public view', tags: ['projects'], responses: { 200: { diff --git a/api/src/paths/search.ts b/api/src/paths/search.ts index 43aa371a37..0a3e422b6d 100644 --- a/api/src/paths/search.ts +++ b/api/src/paths/search.ts @@ -24,7 +24,7 @@ export const GET: Operation = [ ]; GET.apiDoc = { - description: 'Gets a list of published project geometries for given systemUserId', + description: 'Gets a list of project geometries for given systemUserId', tags: ['projects'], security: [ { diff --git a/api/src/paths/xlsx/validate.ts b/api/src/paths/xlsx/validate.ts index 35a0680768..9b184b1e2e 100644 --- a/api/src/paths/xlsx/validate.ts +++ b/api/src/paths/xlsx/validate.ts @@ -82,10 +82,9 @@ export function prepXLSX(): RequestHandler { const xlsxCsv = new XLSXCSV(parsedMedia); const template_id = xlsxCsv.workbook.rawWorkbook.Custprops.sims_template_id; - const species_id = xlsxCsv.workbook.rawWorkbook.Custprops.sims_species_id; const csm_id = xlsxCsv.workbook.rawWorkbook.Custprops.sims_csm_id; - if (!template_id || !species_id || !csm_id) { + if (!template_id || !csm_id) { req['parseError'] = 'Failed to parse submission, template identification properties are missing'; } diff --git a/api/src/queries/dwc/dwc-queries.ts b/api/src/queries/dwc/dwc-queries.ts index 7c9f7d24f3..36bd7b95f0 100644 --- a/api/src/queries/dwc/dwc-queries.ts +++ b/api/src/queries/dwc/dwc-queries.ts @@ -81,7 +81,6 @@ export const getSurveySQL = (surveyId: number): SQLStatement => { end_date, location_description, location_name, - publish_timestamp, create_date, create_user, update_date, @@ -119,7 +118,6 @@ export const getProjectSQL = (projectId: number): SQLStatement => { coordinator_email_address, coordinator_agency_name, coordinator_public, - publish_timestamp, create_date, create_user, update_date, diff --git a/api/src/queries/permit/index.ts b/api/src/queries/permit/index.ts deleted file mode 100644 index 639ee81258..0000000000 --- a/api/src/queries/permit/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import * as permitCreate from './permit-create-queries'; -import * as permitUpdate from './permit-update-queries'; -import * as permitView from './permit-view-queries'; - -export default { ...permitCreate, ...permitUpdate, ...permitView }; diff --git a/api/src/queries/permit/permit-create-queries.test.ts b/api/src/queries/permit/permit-create-queries.test.ts deleted file mode 100644 index 16594109fb..0000000000 --- a/api/src/queries/permit/permit-create-queries.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { expect } from 'chai'; -import { describe } from 'mocha'; -import { IPostPermitNoSampling } from '../../models/permit-no-sampling'; -import { PostCoordinatorData } from '../../models/project-create'; -import { postPermitNoSamplingSQL, postProjectPermitSQL } from './permit-create-queries'; - -describe('postPermitNoSamplingSQL', () => { - const data = { - permit_number: '123', - permit_type: 'permit type', - first_name: 'first', - last_name: 'last', - email_address: 'email', - coordinator_agency: 'agency', - share_contact_details: false - }; - - it('returns null when no noSamplePermit provided', () => { - const response = postPermitNoSamplingSQL((null as unknown) as IPostPermitNoSampling & PostCoordinatorData, 1); - - expect(response).to.be.null; - }); - - it('returns null when no systemUserId provided', () => { - const response = postPermitNoSamplingSQL(data, null); - - expect(response).to.be.null; - }); - - it('returns a SQLStatement when all fields are passed in as expected', () => { - const response = postPermitNoSamplingSQL(data, 1); - - expect(response).to.not.be.null; - - expect(response?.values.length).to.equal(7); - - expect(response?.values).to.deep.include('123'); - expect(response?.values).to.deep.include('first'); - expect(response?.values).to.deep.include('last'); - expect(response?.values).to.deep.include('email'); - expect(response?.values).to.deep.include('agency'); - }); -}); - -describe('postProjectPermitSQL', () => { - it('returns null when no permit number', () => { - const response = postProjectPermitSQL((null as unknown) as string, 'type', 1, 1); - - expect(response).to.be.null; - }); - - it('returns null when no permit type', () => { - const response = postProjectPermitSQL('123', (null as unknown) as string, 1, 1); - - expect(response).to.be.null; - }); - - it('returns null when no project id', () => { - const response = postProjectPermitSQL('123', 'type', (null as unknown) as number, 1); - - expect(response).to.be.null; - }); - - it('returns null when no system user id', () => { - const response = postProjectPermitSQL('123', 'type', 1, (null as unknown) as number); - - expect(response).to.be.null; - }); - - it('returns a SQLStatement when all fields are passed in as expected', () => { - const response = postProjectPermitSQL('123', 'type', 123, 2); - - expect(response).to.not.be.null; - expect(response?.values).to.deep.include('123'); - }); -}); diff --git a/api/src/queries/permit/permit-create-queries.ts b/api/src/queries/permit/permit-create-queries.ts deleted file mode 100644 index 21d8937e66..0000000000 --- a/api/src/queries/permit/permit-create-queries.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { SQL, SQLStatement } from 'sql-template-strings'; -import { IPostPermitNoSampling } from '../../models/permit-no-sampling'; -import { PostCoordinatorData } from '../../models/project-create'; - -/** - * SQL query to insert a permit row for permit associated to a project. - * - * @param {string} permitNumber - * @param {string} permitType - * @param {number} projectId - * @param {number} systemUserId - * @returns {SQLStatement} sql query object - */ -export const postProjectPermitSQL = ( - permitNumber: string, - permitType: string, - projectId: number, - systemUserId: number -): SQLStatement | null => { - if (!permitNumber || !permitType || !projectId || !systemUserId) { - return null; - } - - const sqlStatement: SQLStatement = SQL` - INSERT INTO permit ( - project_id, - number, - type, - system_user_id - ) VALUES ( - ${projectId}, - ${permitNumber}, - ${permitType}, - ${systemUserId} - ) - RETURNING - permit_id as id; - `; - - return sqlStatement; -}; - -/** - * SQL query to insert a no sample permit row. - * - * @param {(IPostPermit & PostCoordinatorData)} noSamplePermit - * @param {number | null} systemUserId - * @returns {SQLStatement} sql query object - */ -export const postPermitNoSamplingSQL = ( - noSamplePermit: IPostPermitNoSampling & PostCoordinatorData, - systemUserId: number | null -): SQLStatement | null => { - if (!noSamplePermit || !systemUserId) { - return null; - } - - const sqlStatement: SQLStatement = SQL` - INSERT INTO permit ( - number, - type, - coordinator_first_name, - coordinator_last_name, - coordinator_email_address, - coordinator_agency_name, - system_user_id - ) VALUES ( - ${noSamplePermit.permit_number}, - ${noSamplePermit.permit_type}, - ${noSamplePermit.first_name}, - ${noSamplePermit.last_name}, - ${noSamplePermit.email_address}, - ${noSamplePermit.coordinator_agency}, - ${systemUserId} - ) - RETURNING - permit_id as id; - `; - - return sqlStatement; -}; diff --git a/api/src/queries/permit/permit-update-queries.test.ts b/api/src/queries/permit/permit-update-queries.test.ts deleted file mode 100644 index 435b39d993..0000000000 --- a/api/src/queries/permit/permit-update-queries.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { expect } from 'chai'; -import { describe } from 'mocha'; -import { associatePermitToProjectSQL } from './permit-update-queries'; - -describe('associatePermitToProjectSQL', () => { - it('returns null when no permit id', () => { - const response = associatePermitToProjectSQL((null as unknown) as number, 1); - - expect(response).to.be.null; - }); - - it('returns null when no project id', () => { - const response = associatePermitToProjectSQL(1, (null as unknown) as number); - - expect(response).to.be.null; - }); - - it('returns a non null response when all fields are passed in as expected', () => { - const response = associatePermitToProjectSQL(123, 2); - - expect(response).to.not.be.null; - expect(response?.values).to.deep.include(123); - }); -}); diff --git a/api/src/queries/permit/permit-update-queries.ts b/api/src/queries/permit/permit-update-queries.ts deleted file mode 100644 index 5bb02d3aa5..0000000000 --- a/api/src/queries/permit/permit-update-queries.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { SQL, SQLStatement } from 'sql-template-strings'; - -/** - * SQL query to associate existing non-sampling permits to a project - * - * @param {number} permitId - * @param {number} projectId - * @returns {SQLStatement} sql query object - */ -export const associatePermitToProjectSQL = (permitId: number, projectId: number): SQLStatement | null => { - if (!permitId || !projectId) { - return null; - } - - return SQL` - UPDATE permit - SET - project_id = ${projectId}, - coordinator_first_name = NULL, - coordinator_last_name = NULL, - coordinator_email_address = NULL, - coordinator_agency_name = NULL - WHERE - permit_id = ${permitId}; - `; -}; diff --git a/api/src/queries/permit/permit-view-queries.test.ts b/api/src/queries/permit/permit-view-queries.test.ts deleted file mode 100644 index f57a5d822f..0000000000 --- a/api/src/queries/permit/permit-view-queries.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { expect } from 'chai'; -import { describe } from 'mocha'; -import { getAllPermitsSQL, getNonSamplingPermitsSQL } from './permit-view-queries'; - -describe('getNonSamplingPermitsSQL', () => { - it('returns null when no system user id', () => { - const response = getNonSamplingPermitsSQL((null as unknown) as number); - - expect(response).to.be.null; - }); - - it('returns a non null response when all fields are passed in as expected', () => { - const response = getNonSamplingPermitsSQL(1); - - expect(response).to.not.be.null; - }); -}); - -describe('getAllPermitsSQL', () => { - it('returns null when no system user id', () => { - const response = getAllPermitsSQL((null as unknown) as number); - - expect(response).to.be.null; - }); - - it('returns a non null response when all fields are passed in as expected', () => { - const response = getAllPermitsSQL(1); - - expect(response).to.not.be.null; - }); -}); diff --git a/api/src/queries/permit/permit-view-queries.ts b/api/src/queries/permit/permit-view-queries.ts deleted file mode 100644 index c90d8a701d..0000000000 --- a/api/src/queries/permit/permit-view-queries.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { SQL, SQLStatement } from 'sql-template-strings'; - -/** - * SQL query to get all non-sampling permits - * - * @param {number | null} systemUserId - * @returns {SQLStatement} sql query object - */ -export const getNonSamplingPermitsSQL = (systemUserId: number | null): SQLStatement | null => { - if (!systemUserId) { - return null; - } - - return SQL` - SELECT - permit_id, - number, - type - FROM - permit - WHERE - system_user_id = ${systemUserId} - AND - project_id IS NULL; - `; -}; - -/** - * SQL query to get all permit numbers by system user id - * - * @param {number | null} systemUserId - * @returns {SQLStatement} sql query object - */ -export const getAllPermitsSQL = (systemUserId: number | null): SQLStatement | null => { - if (!systemUserId) { - return null; - } - - return SQL` - SELECT - per.permit_id as id, - per.number, - per.type, - CASE - WHEN per.project_id IS NULL THEN per.coordinator_agency_name - WHEN per.project_id IS NOT NULL THEN p.coordinator_agency_name - END as coordinator_agency, - p.name as project_name - FROM - permit as per - LEFT OUTER JOIN - project as p - ON - p.project_id = per.project_id - WHERE - system_user_id = ${systemUserId}; - `; -}; diff --git a/api/src/queries/project/project-delete-queries.test.ts b/api/src/queries/project/project-delete-queries.test.ts index 72a8c590ed..d6d1c7440c 100644 --- a/api/src/queries/project/project-delete-queries.test.ts +++ b/api/src/queries/project/project-delete-queries.test.ts @@ -4,7 +4,6 @@ import { deleteActivitiesSQL, deleteIndigenousPartnershipsSQL, deleteIUCNSQL, - deletePermitSQL, deleteProjectFundingSourceSQL, deleteProjectSQL, deleteStakeholderPartnershipsSQL @@ -24,20 +23,6 @@ describe('deleteIUCNSQL', () => { }); }); -describe('deletePermitSQL', () => { - it('returns null response when null projectId provided', () => { - const response = deletePermitSQL((null as unknown) as number); - - expect(response).to.be.null; - }); - - it('returns non null response when valid projectId provided', () => { - const response = deletePermitSQL(1); - - expect(response).to.not.be.null; - }); -}); - describe('deleteIndigenousPartnershipsSQL', () => { it('returns null response when null projectId provided', () => { const response = deleteIndigenousPartnershipsSQL((null as unknown) as number); diff --git a/api/src/queries/project/project-delete-queries.ts b/api/src/queries/project/project-delete-queries.ts index 2cbffdc999..936d19afbf 100644 --- a/api/src/queries/project/project-delete-queries.ts +++ b/api/src/queries/project/project-delete-queries.ts @@ -21,27 +21,6 @@ export const deleteIndigenousPartnershipsSQL = (projectId: number): SQLStatement return sqlStatement; }; -/** - * SQL query to delete permit rows associated to a project - * - * @param {projectId} projectId - * @returns {SQLStatement} sql query object - */ -export const deletePermitSQL = (projectId: number): SQLStatement | null => { - if (!projectId) { - return null; - } - - const sqlStatement: SQLStatement = SQL` - DELETE - from permit - WHERE - project_id = ${projectId}; - `; - - return sqlStatement; -}; - /** * SQL query to delete project stakeholder partnership rows * diff --git a/api/src/queries/project/project-update-queries.test.ts b/api/src/queries/project/project-update-queries.test.ts index f7cf2df7df..e21cb97f90 100644 --- a/api/src/queries/project/project-update-queries.test.ts +++ b/api/src/queries/project/project-update-queries.test.ts @@ -12,11 +12,9 @@ import { getIndigenousPartnershipsByProjectSQL, getIUCNActionClassificationByProjectSQL, getObjectivesByProjectSQL, - getPermitsByProjectSQL, getProjectByProjectSQL, putProjectFundingSourceSQL, - putProjectSQL, - updateProjectPublishStatusSQL + putProjectSQL } from './project-update-queries'; describe('getIndigenousPartnershipsByProjectSQL', () => { @@ -33,20 +31,6 @@ describe('getIndigenousPartnershipsByProjectSQL', () => { }); }); -describe('getPermitsByProjectSQL', () => { - it('Null projectId', () => { - const response = getPermitsByProjectSQL((null as unknown) as number); - - expect(response).to.be.null; - }); - - it('valid projectId', () => { - const response = getPermitsByProjectSQL(1); - - expect(response).to.not.be.null; - }); -}); - describe('getIUCNActionClassificationByProjectSQL', () => { it('returns null response when null projectId provided', () => { const response = getIUCNActionClassificationByProjectSQL((null as unknown) as number); @@ -62,12 +46,6 @@ describe('getIUCNActionClassificationByProjectSQL', () => { }); describe('getCoordinatorByProjectSQL', () => { - it('Null projectId', () => { - const response = getCoordinatorByProjectSQL((null as unknown) as number); - - expect(response).to.be.null; - }); - it('valid projectId', () => { const response = getCoordinatorByProjectSQL(1); @@ -217,12 +195,6 @@ describe('putProjectSQL', () => { }); describe('getObjectivesByProjectSQL', () => { - it('Null projectId', () => { - const response = getObjectivesByProjectSQL((null as unknown) as number); - - expect(response).to.be.null; - }); - it('valid projectId', () => { const response = getObjectivesByProjectSQL(1); @@ -272,29 +244,3 @@ describe('putProjectFundingSourceSQL', () => { }); }); }); - -describe('updateProjectPublishStatusSQL', () => { - describe('with invalid parameters', () => { - it('returns null when project is null', () => { - const response = updateProjectPublishStatusSQL((null as unknown) as number, true); - - expect(response).to.be.null; - }); - }); - - describe('with valid parameters', () => { - it('returns a SQLStatement when there is a real date value', () => { - const response = updateProjectPublishStatusSQL(1, true); - - expect(response).to.not.be.null; - expect(response?.values).to.deep.include(1); - }); - - it('returns a SQLStatement when the date value is null', () => { - const response = updateProjectPublishStatusSQL(1, false); - - expect(response).to.not.be.null; - expect(response?.values).to.deep.include(1); - }); - }); -}); diff --git a/api/src/queries/project/project-update-queries.ts b/api/src/queries/project/project-update-queries.ts index 913a219027..4e99aa69fa 100644 --- a/api/src/queries/project/project-update-queries.ts +++ b/api/src/queries/project/project-update-queries.ts @@ -69,38 +69,13 @@ export const getIndigenousPartnershipsByProjectSQL = (projectId: number): SQLSta `; }; -/** - * SQL query to get permits associated to a project. - * @param {number} projectId - * @returns {SQLStatement} sql query object - */ -export const getPermitsByProjectSQL = (projectId: number): SQLStatement | null => { - if (!projectId) { - return null; - } - - return SQL` - SELECT - number, - type - FROM - permit - WHERE - project_id = ${projectId}; - `; -}; - /** * SQL query to get coordinator information, for update purposes. * * @param {number} projectId * @return {*} {(SQLStatement | null)} */ -export const getCoordinatorByProjectSQL = (projectId: number): SQLStatement | null => { - if (!projectId) { - return null; - } - +export const getCoordinatorByProjectSQL = (projectId: number): SQLStatement => { return SQL` SELECT coordinator_first_name, @@ -238,11 +213,7 @@ export const putProjectSQL = ( * @param {number} projectId * @return {*} {(SQLStatement | null)} */ -export const getObjectivesByProjectSQL = (projectId: number): SQLStatement | null => { - if (!projectId) { - return null; - } - +export const getObjectivesByProjectSQL = (projectId: number): SQLStatement => { return SQL` SELECT objectives, @@ -289,31 +260,3 @@ export const putProjectFundingSourceSQL = ( project_funding_source_id as id; `; }; - -/** - * SQL query to update the publish status of a project. - * - * @param {number} projectId - * @param {boolean} publish - * @returns {SQLStatement} sql query object - */ -export const updateProjectPublishStatusSQL = (projectId: number, publish: boolean): SQLStatement | null => { - if (!projectId) { - return null; - } - - const sqlStatement: SQLStatement = SQL`UPDATE project SET publish_timestamp = `; - - if (publish === true) { - sqlStatement.append(SQL` - now() WHERE publish_timestamp IS NULL AND project_id = ${projectId} - `); - } else { - sqlStatement.append(SQL` - null WHERE project_id = ${projectId} - `); - } - sqlStatement.append(SQL` RETURNING project_id as id;`); - - return sqlStatement; -}; diff --git a/api/src/queries/project/project-view-queries.test.ts b/api/src/queries/project/project-view-queries.test.ts index f81896f1f9..32b9515a9b 100644 --- a/api/src/queries/project/project-view-queries.test.ts +++ b/api/src/queries/project/project-view-queries.test.ts @@ -2,26 +2,18 @@ import { expect } from 'chai'; import { describe } from 'mocha'; import { getActivitiesByProjectSQL, + getAttachmentsByProjectSQL, getFundingSourceByProjectSQL, getIndigenousPartnershipsByProjectSQL, getIUCNActionClassificationByProjectSQL, getLocationByProjectSQL, getProjectListSQL, - getProjectPermitsSQL, getProjectSQL, + getReportAttachmentsByProjectSQL, getStakeholderPartnershipsByProjectSQL } from './project-view-queries'; describe('getProjectSQL', () => { - describe('Null project id param provided', () => { - it('returns null', () => { - // force the function to accept a null value - const response = getProjectSQL((null as unknown) as number); - - expect(response).to.be.null; - }); - }); - describe('Valid project id param provided', () => { it('returns a SQLStatement', () => { const response = getProjectSQL(1); @@ -56,12 +48,6 @@ describe('getProjectListSQL', () => { expect(response).to.not.be.null; }); - it('returns a SQLStatement when filter fields provided (only permit number)', () => { - const response = getProjectListSQL(true, 1, { permit_number: '123' }); - - expect(response).to.not.be.null; - }); - it('returns a SQLStatement when filter fields provided (only project type)', () => { const response = getProjectListSQL(true, 1, { project_type: 'type' }); @@ -118,12 +104,6 @@ describe('getProjectListSQL', () => { }); describe('getIUCNActionClassificationByProjectSQL', () => { - it('returns null response when null projectId provided', () => { - const response = getIUCNActionClassificationByProjectSQL((null as unknown) as number); - - expect(response).to.be.null; - }); - it('returns non null response when valid projectId provided', () => { const response = getIUCNActionClassificationByProjectSQL(1); @@ -159,57 +139,53 @@ describe('getStakeholderPartnershipsByProjectSQL', () => { }); }); -describe('getProjectPermitsSQL', () => { - it('Null projectId', () => { - const response = getProjectPermitsSQL((null as unknown) as number); - - expect(response).to.be.null; - }); - +describe('getLocationByProjectSQL', () => { it('valid projectId', () => { - const response = getProjectPermitsSQL(1); + const response = getLocationByProjectSQL(1); expect(response).to.not.be.null; }); }); -describe('getLocationByProjectSQL', () => { - it('Null projectId', () => { - const response = getLocationByProjectSQL((null as unknown) as number); +describe('getActivitiesByProjectSQL', () => { + it('valid projectId', () => { + const response = getActivitiesByProjectSQL(1); - expect(response).to.be.null; + expect(response).to.not.be.null; }); +}); +describe('getFundingSourceByProjectSQL', () => { it('valid projectId', () => { - const response = getLocationByProjectSQL(1); + const response = getFundingSourceByProjectSQL(1); expect(response).to.not.be.null; }); }); -describe('getActivitiesByProjectSQL', () => { +describe('getAttachmentsByProjectSQL', () => { it('Null projectId', () => { - const response = getActivitiesByProjectSQL((null as unknown) as number); + const response = getAttachmentsByProjectSQL((null as unknown) as number); expect(response).to.be.null; }); it('valid projectId', () => { - const response = getActivitiesByProjectSQL(1); + const response = getAttachmentsByProjectSQL(1); expect(response).to.not.be.null; }); }); -describe('getFundingSourceByProjectSQL', () => { +describe('getReportAttachmentsByProjectSQL', () => { it('Null projectId', () => { - const response = getFundingSourceByProjectSQL((null as unknown) as number); + const response = getReportAttachmentsByProjectSQL((null as unknown) as number); expect(response).to.be.null; }); it('valid projectId', () => { - const response = getFundingSourceByProjectSQL(1); + const response = getReportAttachmentsByProjectSQL(1); expect(response).to.not.be.null; }); diff --git a/api/src/queries/project/project-view-queries.ts b/api/src/queries/project/project-view-queries.ts index 616bb26eef..b45a10ee16 100644 --- a/api/src/queries/project/project-view-queries.ts +++ b/api/src/queries/project/project-view-queries.ts @@ -6,11 +6,7 @@ import { SQL, SQLStatement } from 'sql-template-strings'; * @param {number} projectId * @returns {SQLStatement} sql query object */ -export const getProjectSQL = (projectId: number): SQLStatement | null => { - if (!projectId) { - return null; - } - +export const getProjectSQL = (projectId: number): SQLStatement => { return SQL` SELECT project.project_id as id, @@ -34,8 +30,7 @@ export const getProjectSQL = (projectId: number): SQLStatement | null => { project.create_user, project.update_date, project.update_user, - project.revision_count, - project.publish_timestamp as publish_date + project.revision_count from project left outer join @@ -70,15 +65,11 @@ export const getProjectListSQL = ( p.start_date, p.end_date, p.coordinator_agency_name as coordinator_agency, - p.publish_timestamp, - pt.name as project_type, - string_agg(DISTINCT pp.number, ', ') as permits_list + pt.name as project_type from project as p left outer join project_type as pt on p.project_type_id = pt.project_type_id - left outer join permit as pp - on p.project_id = pp.project_id left outer join project_funding_source as pfs on pfs.project_id = p.project_id left outer join investment_action_category as iac @@ -124,10 +115,6 @@ export const getProjectListSQL = ( ); } - if (filterFields.permit_number) { - sqlStatement.append(SQL` AND pp.number = ${filterFields.permit_number}`); - } - if (filterFields.project_type) { sqlStatement.append(SQL` AND pt.name = ${filterFields.project_type}`); } @@ -164,7 +151,6 @@ export const getProjectListSQL = ( p.start_date, p.end_date, p.coordinator_agency_name, - p.publish_timestamp, pt.name; `); @@ -177,11 +163,7 @@ export const getProjectListSQL = ( * @param {number} projectId * @returns {SQLStatement} sql query object */ -export const getIUCNActionClassificationByProjectSQL = (projectId: number): SQLStatement | null => { - if (!projectId) { - return null; - } - +export const getIUCNActionClassificationByProjectSQL = (projectId: number): SQLStatement => { return SQL` SELECT ical1c.iucn_conservation_action_level_1_classification_id as classification, @@ -261,38 +243,73 @@ export const getStakeholderPartnershipsByProjectSQL = (projectId: number): SQLSt }; /** - * SQL query to get permits associated to a project. + * SQL query to get project attachments. * * @param {number} projectId * @returns {SQLStatement} sql query object */ -export const getProjectPermitsSQL = (projectId: number): SQLStatement | null => { +export const getAttachmentsByProjectSQL = (projectId: number): SQLStatement | null => { if (!projectId) { return null; } return SQL` SELECT - number, - type + * FROM - permit + project_attachment WHERE - project_id = ${projectId} + project_id = ${projectId}; `; }; /** - * SQL query to get project location. + * SQL query to get project reports. * * @param {number} projectId * @returns {SQLStatement} sql query object */ -export const getLocationByProjectSQL = (projectId: number): SQLStatement | null => { +export const getReportAttachmentsByProjectSQL = (projectId: number): SQLStatement | null => { if (!projectId) { return null; } + return SQL` + SELECT + pra.project_report_attachment_id + , pra.project_id + , pra.file_name + , pra.title + , pra.description + , pra.year + , pra."key" + , pra.file_size + , pra.security_token + , array_remove(array_agg(pra2.first_name ||' '||pra2.last_name), null) authors + FROM + project_report_attachment pra + LEFT JOIN project_report_author pra2 ON pra2.project_report_attachment_id = pra.project_report_attachment_id + WHERE pra.project_id = ${projectId} + GROUP BY + pra.project_report_attachment_id + , pra.project_id + , pra.file_name + , pra.title + , pra.description + , pra.year + , pra."key" + , pra.file_size + , pra.security_token; + `; +}; + +/** + * SQL query to get project location. + * + * @param {number} projectId + * @returns {SQLStatement} sql query object + */ +export const getLocationByProjectSQL = (projectId: number): SQLStatement => { return SQL` SELECT p.location_description, @@ -316,11 +333,7 @@ export const getLocationByProjectSQL = (projectId: number): SQLStatement | null * @returns {SQLStatement} sql query object */ -export const getActivitiesByProjectSQL = (projectId: number): SQLStatement | null => { - if (!projectId) { - return null; - } - +export const getActivitiesByProjectSQL = (projectId: number): SQLStatement => { return SQL` SELECT activity_id @@ -336,11 +349,7 @@ export const getActivitiesByProjectSQL = (projectId: number): SQLStatement | nul * @param {number} projectId * @returns {SQLStatement} sql query object */ -export const getFundingSourceByProjectSQL = (projectId: number): SQLStatement | null => { - if (!projectId) { - return null; - } - +export const getFundingSourceByProjectSQL = (projectId: number): SQLStatement => { return SQL` SELECT pfs.project_funding_source_id as id, diff --git a/api/src/queries/public/project-queries.ts b/api/src/queries/public/project-queries.ts index f07fddc990..49ab56741e 100644 --- a/api/src/queries/public/project-queries.ts +++ b/api/src/queries/public/project-queries.ts @@ -1,7 +1,7 @@ import { SQL, SQLStatement } from 'sql-template-strings'; /** - * SQL query to get a single public (published) project. + * SQL query to get a single public project. * * @param {number} projectId * @returns {SQLStatement} sql query object @@ -22,21 +22,19 @@ export const getPublicProjectSQL = (projectId: number): SQLStatement | null => { project.end_date, project.caveats, project.comments, - project.geojson as geometry, - project.publish_timestamp as publish_date + project.geojson as geometry from project left outer join project_type on project.project_type_id = project_type.project_type_id where - project.project_id = ${projectId} - and project.publish_timestamp is not null; + project.project_id = ${projectId}; `; }; /** - * SQL query to get public (published) project activities. + * SQL query to get public project activities. * * @param {string} projectId * @returns {SQLStatement} sql query object @@ -57,14 +55,12 @@ export const getActivitiesByPublicProjectSQL = (projectId: number): SQLStatement ON p.project_id = pa.project_id WHERE - pa.project_id = ${projectId} - AND - p.publish_timestamp is not null; + pa.project_id = ${projectId}; `; }; /** - * SQL query to get all public facing (published) projects. + * SQL query to get all public facing projects. * * @returns {SQLStatement} sql query object */ @@ -76,16 +72,11 @@ export const getPublicProjectListSQL = (): SQLStatement | null => { p.start_date, p.end_date, p.coordinator_agency_name as coordinator_agency, - pt.name as project_type, - string_agg(DISTINCT pp.number, ', ') as permits_list + pt.name as project_type from project as p left outer join project_type as pt on p.project_type_id = pt.project_type_id - left outer join permit as pp - on p.project_id = pp.project_id - where - p.publish_timestamp is not null group by p.project_id, p.name, @@ -97,7 +88,7 @@ export const getPublicProjectListSQL = (): SQLStatement | null => { }; /** - * SQL query to get attachments for a single public (published) project. + * SQL query to get attachments for a single public project. * * @param {number} projectId * @returns {SQLStatement} sql query object @@ -123,14 +114,12 @@ export const getPublicProjectAttachmentsSQL = (projectId: number): SQLStatement on p.project_id = pa.project_id where - pa.project_id = ${projectId} - and - p.publish_timestamp is not null; + pa.project_id = ${projectId}; `; }; /** - * SQL query to get report attachments for a single public (published) project. + * SQL query to get report attachments for a single public project. * * @param {number} projectId * @returns {SQLStatement} sql query object @@ -155,14 +144,12 @@ export const getPublicProjectReportAttachmentsSQL = (projectId: number): SQLStat on p.project_id = pa.project_id where - pa.project_id = ${projectId} - and - p.publish_timestamp is not null; + pa.project_id = ${projectId}; `; }; /** - * SQL query to get S3 key of an attachment for a single public (published) project. + * SQL query to get S3 key of an attachment for a single public project. * * @param {number} projectId * @param {number} attachmentId @@ -187,7 +174,7 @@ export const getPublicProjectAttachmentS3KeySQL = (projectId: number, attachment }; /** - * SQL query to get S3 key of a report attachment for a single public (published) project. + * SQL query to get S3 key of a report attachment for a single public project. * * @param {number} projectId * @param {number} attachmentId diff --git a/api/src/queries/public/search-queries.ts b/api/src/queries/public/search-queries.ts index d70adb3e11..329a006c3a 100644 --- a/api/src/queries/public/search-queries.ts +++ b/api/src/queries/public/search-queries.ts @@ -12,8 +12,6 @@ export const getPublicSpatialSearchResultsSQL = (): SQLStatement | null => { p.name, public.ST_asGeoJSON(p.geography) as geometry from - project as p - where - p.publish_timestamp is not null; + project as p; `; }; diff --git a/api/src/queries/queries.ts b/api/src/queries/queries.ts index ef069829d4..00c350e1c3 100644 --- a/api/src/queries/queries.ts +++ b/api/src/queries/queries.ts @@ -3,7 +3,6 @@ import codes from './codes'; import database from './database'; import dwc from './dwc'; import occurrence from './occurrence'; -import permit from './permit'; import project from './project'; import projectParticipation from './project-participation'; import publicQueries from './public'; @@ -19,7 +18,6 @@ export const queries = { database, dwc, occurrence, - permit, project, projectParticipation, public: publicQueries, diff --git a/api/src/queries/search/search-queries.ts b/api/src/queries/search/search-queries.ts index 81e438b1f1..9c5b6fca97 100644 --- a/api/src/queries/search/search-queries.ts +++ b/api/src/queries/search/search-queries.ts @@ -18,9 +18,7 @@ export const getSpatialSearchResultsSQL = (isUserAdmin: boolean, systemUserId: n p.name, public.ST_asGeoJSON(p.geography) as geometry from - project as p - where - p.publish_timestamp is not null + project as p; `; if (!isUserAdmin) { diff --git a/api/src/queries/survey/survey-create-queries.test.ts b/api/src/queries/survey/survey-create-queries.test.ts index d9290f9200..22a1f76a67 100644 --- a/api/src/queries/survey/survey-create-queries.test.ts +++ b/api/src/queries/survey/survey-create-queries.test.ts @@ -11,19 +11,6 @@ import { } from './survey-create-queries'; describe('postSurveySQL', () => { - it('returns null when null projectId param provided', () => { - const survey = new PostSurveyObject(); - const response = postSurveySQL((null as unknown) as number, survey); - - expect(response).to.be.null; - }); - - it('returns null when null survey data param provided', () => { - const response = postSurveySQL(1, (null as unknown) as PostSurveyObject); - - expect(response).to.be.null; - }); - it('returns a sql statement when geometry array is empty', () => { const surveyData = { survey_details: { @@ -113,18 +100,6 @@ describe('postSurveyProprietorSQL', () => { }); describe('postFocalSpeciesSQL', () => { - it('returns null when null speciesId provided', () => { - const response = postFocalSpeciesSQL((null as unknown) as number, 1); - - expect(response).to.be.null; - }); - - it('returns null when null surveyId provided', () => { - const response = postFocalSpeciesSQL(1, (null as unknown) as number); - - expect(response).to.be.null; - }); - it('returns sql statement when valid params provided', () => { const response = postFocalSpeciesSQL(1, 2); @@ -133,18 +108,6 @@ describe('postFocalSpeciesSQL', () => { }); describe('postAncillarySpeciesSQL', () => { - it('returns null when null speciesId provided', () => { - const response = postAncillarySpeciesSQL((null as unknown) as number, 1); - - expect(response).to.be.null; - }); - - it('returns null when null surveyId provided', () => { - const response = postAncillarySpeciesSQL(1, (null as unknown) as number); - - expect(response).to.be.null; - }); - it('returns sql statement when valid params provided', () => { const response = postAncillarySpeciesSQL(1, 2); @@ -153,36 +116,6 @@ describe('postAncillarySpeciesSQL', () => { }); describe('postNewSurveyPermitSQL', () => { - it('returns null when null projectId provided', () => { - const response = postNewSurveyPermitSQL(1, (null as unknown) as number, 1, '123', 'scientific'); - - expect(response).to.be.null; - }); - - it('returns null when null surveyId provided', () => { - const response = postNewSurveyPermitSQL(1, 1, (null as unknown) as number, '123', 'scientific'); - - expect(response).to.be.null; - }); - - it('returns null when null permitNumber provided', () => { - const response = postNewSurveyPermitSQL(1, 1, 2, (null as unknown) as string, 'scientific'); - - expect(response).to.be.null; - }); - - it('returns null when null permitType provided', () => { - const response = postNewSurveyPermitSQL(1, 1, 2, '123', (null as unknown) as string); - - expect(response).to.be.null; - }); - - it('returns null when null systemUserId provided', () => { - const response = postNewSurveyPermitSQL(null, 1, 2, '123', 'scientific'); - - expect(response).to.be.null; - }); - it('returns sql statement when valid params provided', () => { const response = postNewSurveyPermitSQL(1, 1, 2, '123', 'scientific'); @@ -191,18 +124,6 @@ describe('postNewSurveyPermitSQL', () => { }); describe('insertSurveyFundingSourceSQL', () => { - it('returns null when null surveyId provided', () => { - const response = insertSurveyFundingSourceSQL((null as unknown) as number, 1); - - expect(response).to.be.null; - }); - - it('returns null when null fundingSourceId provided', () => { - const response = insertSurveyFundingSourceSQL(1, (null as unknown) as number); - - expect(response).to.be.null; - }); - it('returns sql statement when valid params provided', () => { const response = insertSurveyFundingSourceSQL(1, 2); diff --git a/api/src/queries/survey/survey-create-queries.ts b/api/src/queries/survey/survey-create-queries.ts index 833bed1103..bd90855cf8 100644 --- a/api/src/queries/survey/survey-create-queries.ts +++ b/api/src/queries/survey/survey-create-queries.ts @@ -9,11 +9,7 @@ import { queries } from '../queries'; * @param {PostSurveyObject} survey * @returns {SQLStatement} sql query object */ -export const postSurveySQL = (projectId: number, survey: PostSurveyObject): SQLStatement | null => { - if (!projectId || !survey) { - return null; - } - +export const postSurveySQL = (projectId: number, survey: PostSurveyObject): SQLStatement => { const sqlStatement: SQLStatement = SQL` INSERT INTO survey ( project_id, @@ -111,11 +107,7 @@ export const postSurveyProprietorSQL = (surveyId: number, survey_proprietor: Pos * @param {number} fundingSourceId * @returns {SQLStatement} sql query object */ -export const insertSurveyFundingSourceSQL = (surveyId: number, fundingSourceId: number): SQLStatement | null => { - if (!surveyId || !fundingSourceId) { - return null; - } - +export const insertSurveyFundingSourceSQL = (surveyId: number, fundingSourceId: number): SQLStatement => { const sqlStatement: SQLStatement = SQL` INSERT INTO survey_funding_source ( survey_id, @@ -132,7 +124,7 @@ export const insertSurveyFundingSourceSQL = (surveyId: number, fundingSourceId: /** * SQL query to insert a survey permit row into the permit table. * - * @param {number | null} systemUserId + * @param {number } systemUserId * @param {number} projectId * @param {number} surveyId * @param {string} permitNumber @@ -140,16 +132,12 @@ export const insertSurveyFundingSourceSQL = (surveyId: number, fundingSourceId: * @returns {SQLStatement} sql query object */ export const postNewSurveyPermitSQL = ( - systemUserId: number | null, + systemUserId: number, projectId: number, surveyId: number, permitNumber: string, permitType: string -): SQLStatement | null => { - if (!systemUserId || !projectId || !surveyId || !permitNumber || !permitType) { - return null; - } - +): SQLStatement => { const sqlStatement: SQLStatement = SQL` INSERT INTO permit ( system_user_id, @@ -176,11 +164,7 @@ export const postNewSurveyPermitSQL = ( * @param {number} surveyId * @returns {SQLStatement} sql query object */ -export const postFocalSpeciesSQL = (speciesId: number, surveyId: number): SQLStatement | null => { - if (!speciesId || !surveyId) { - return null; - } - +export const postFocalSpeciesSQL = (speciesId: number, surveyId: number): SQLStatement => { const sqlStatement: SQLStatement = SQL` INSERT INTO study_species ( wldtaxonomic_units_id, @@ -203,11 +187,7 @@ export const postFocalSpeciesSQL = (speciesId: number, surveyId: number): SQLSta * @param {number} surveyId * @returns {SQLStatement} sql query object */ -export const postAncillarySpeciesSQL = (speciesId: number, surveyId: number): SQLStatement | null => { - if (!speciesId || !surveyId) { - return null; - } - +export const postAncillarySpeciesSQL = (speciesId: number, surveyId: number): SQLStatement => { const sqlStatement: SQLStatement = SQL` INSERT INTO study_species ( wldtaxonomic_units_id, @@ -230,11 +210,7 @@ export const postAncillarySpeciesSQL = (speciesId: number, surveyId: number): SQ * @param {number} surveyId * @returns {SQLStatement} sql query object */ -export const postVantageCodesSQL = (vantageCodeId: number, surveyId: number): SQLStatement | null => { - if (!vantageCodeId || !surveyId) { - return null; - } - +export const postVantageCodesSQL = (vantageCodeId: number, surveyId: number): SQLStatement => { const sqlStatement: SQLStatement = SQL` INSERT INTO survey_vantage ( vantage_id, diff --git a/api/src/queries/survey/survey-occurrence-queries.test.ts b/api/src/queries/survey/survey-occurrence-queries.test.ts index dd3efd5a0d..52531f8615 100644 --- a/api/src/queries/survey/survey-occurrence-queries.test.ts +++ b/api/src/queries/survey/survey-occurrence-queries.test.ts @@ -75,12 +75,6 @@ describe('deleteOccurrenceSubmissionSQL', () => { }); describe('getLatestSurveyOccurrenceSubmission', () => { - it('returns null response when null surveyId provided', () => { - const response = getLatestSurveyOccurrenceSubmissionSQL((null as unknown) as number); - - expect(response).to.be.null; - }); - it('returns non null response when valid params provided', () => { const response = getLatestSurveyOccurrenceSubmissionSQL(1); diff --git a/api/src/queries/survey/survey-occurrence-queries.ts b/api/src/queries/survey/survey-occurrence-queries.ts index 1c765557e5..20ce666f0e 100644 --- a/api/src/queries/survey/survey-occurrence-queries.ts +++ b/api/src/queries/survey/survey-occurrence-queries.ts @@ -145,11 +145,7 @@ export const updateSurveyOccurrenceSubmissionSQL = (data: { * @param {number} surveyId * @returns {SQLStatement} sql query object */ -export const getLatestSurveyOccurrenceSubmissionSQL = (surveyId: number): SQLStatement | null => { - if (!surveyId) { - return null; - } - +export const getLatestSurveyOccurrenceSubmissionSQL = (surveyId: number): SQLStatement => { return SQL` SELECT os.occurrence_submission_id as id, @@ -159,6 +155,8 @@ export const getLatestSurveyOccurrenceSubmissionSQL = (surveyId: number): SQLSta os.event_timestamp, os.input_key, os.input_file_name, + os.output_key, + os.output_file_name, ss.submission_status_id, ss.submission_status_type_id, sst.name as submission_status_type_name, diff --git a/api/src/queries/survey/survey-summary-queries.ts b/api/src/queries/survey/survey-summary-queries.ts index 8543501d68..730f121048 100644 --- a/api/src/queries/survey/survey-summary-queries.ts +++ b/api/src/queries/survey/survey-summary-queries.ts @@ -161,37 +161,51 @@ export const insertSurveySummaryDetailsSQL = ( INSERT INTO survey_summary_detail ( survey_summary_submission_id, study_area_id, + population_unit, + block_sample_unit_id, parameter, stratum, - parameter_value, - parameter_estimate, - confidence_limit_lower, - confidence_limit_upper, - confidence_level_percent, + observed, + estimated, sightability_model, + sightability_correction, standard_error, coefficient_variation, - kilometres_surveyed, + confidence_level_percent, + confidence_limit_lower, + confidence_limit_upper, total_area_surveyed_sqm, + area_flown, + total_kilometers_surveyed, + best_parameter_flag, outlier_blocks_removed, - analysis_method + total_marked_animals_observed, + marked_animals_available, + parameter_comments ) VALUES ( ${summarySubmissionId}, ${summaryDetails.study_area_id}, + ${summaryDetails.population_unit}, + ${summaryDetails.block_sample_unit_id}, ${summaryDetails.parameter}, ${summaryDetails.stratum}, - ${summaryDetails.parameter_value}, - ${summaryDetails.parameter_estimate}, - ${summaryDetails.confidence_limit_lower}, - ${summaryDetails.confidence_limit_upper}, - ${summaryDetails.confidence_level_percent}, + ${summaryDetails.observed}, + ${summaryDetails.estimated}, ${summaryDetails.sightability_model}, + ${summaryDetails.sightability_correction_factor}, ${summaryDetails.standard_error}, ${summaryDetails.coefficient_variation}, - ${summaryDetails.kilometres_surveyed}, + ${summaryDetails.confidence_level_percent}, + ${summaryDetails.confidence_limit_lower}, + ${summaryDetails.confidence_limit_upper}, ${summaryDetails.total_area_survey_sqm}, + ${summaryDetails.area_flown}, + ${summaryDetails.total_kilometers_surveyed}, + ${summaryDetails.best_parameter_flag}, ${summaryDetails.outlier_blocks_removed}, - ${summaryDetails.analysis_method} + ${summaryDetails.total_marked_animals_observed}, + ${summaryDetails.marked_animals_available}, + ${summaryDetails.parameter_comments} ) RETURNING survey_summary_detail_id as id; `; diff --git a/api/src/queries/survey/survey-update-queries.test.ts b/api/src/queries/survey/survey-update-queries.test.ts index db770d9913..30d2b6fdb8 100644 --- a/api/src/queries/survey/survey-update-queries.test.ts +++ b/api/src/queries/survey/survey-update-queries.test.ts @@ -14,8 +14,7 @@ import { associateSurveyToPermitSQL, insertSurveyPermitSQL, putSurveyDetailsSQL, - unassociatePermitFromSurveySQL, - updateSurveyPublishStatusSQL + unassociatePermitFromSurveySQL } from './survey-update-queries'; describe('putSurveyDetailsSQL', () => { @@ -84,29 +83,3 @@ describe('associateSurveyToPermitSQL', () => { expect(response).not.to.be.null; }); }); - -describe('updateSurveyPublishStatusSQL', () => { - describe('with invalid parameters', () => { - it('returns null when survey is null', () => { - const response = updateSurveyPublishStatusSQL((null as unknown) as number, true); - - expect(response).to.be.null; - }); - }); - - describe('with valid parameters', () => { - it('returns a SQLStatement when there is a real date value', () => { - const response = updateSurveyPublishStatusSQL(1, true); - - expect(response).to.not.be.null; - expect(response?.values).to.deep.include(1); - }); - - it('returns a SQLStatement when the date value is null', () => { - const response = updateSurveyPublishStatusSQL(1, false); - - expect(response).to.not.be.null; - expect(response?.values).to.deep.include(1); - }); - }); -}); diff --git a/api/src/queries/survey/survey-update-queries.ts b/api/src/queries/survey/survey-update-queries.ts index 2447200939..1a7cf69087 100644 --- a/api/src/queries/survey/survey-update-queries.ts +++ b/api/src/queries/survey/survey-update-queries.ts @@ -156,47 +156,3 @@ export const putSurveyDetailsSQL = (surveyId: number, data: PutSurveyObject): Kn return knex('survey').update(fieldsToUpdate).where('survey_id', surveyId); }; - -/** - * SQL query to update the publish status of a survey. - * - * @param {number} surveyId - * @param {boolean} publish - * @returns {SQLStatement} sql query object - */ -export const updateSurveyPublishStatusSQL = (surveyId: number, publish: boolean): SQLStatement | null => { - if (!surveyId) { - return null; - } - - const sqlStatement: SQLStatement = SQL` - UPDATE - survey - SET - publish_timestamp = `; - - if (publish) { - sqlStatement.append(SQL` - now() - WHERE - survey_id = ${surveyId} - AND - publish_timestamp IS NULL - `); - } else { - sqlStatement.append(SQL` - null - WHERE - survey_id = ${surveyId} - AND - publish_timestamp IS NOT NULL - `); - } - - sqlStatement.append(SQL` - RETURNING - survey_id as id; - `); - - return sqlStatement; -}; diff --git a/api/src/queries/survey/survey-view-queries.test.ts b/api/src/queries/survey/survey-view-queries.test.ts index fdc5954388..021558d099 100644 --- a/api/src/queries/survey/survey-view-queries.test.ts +++ b/api/src/queries/survey/survey-view-queries.test.ts @@ -2,6 +2,10 @@ import { expect } from 'chai'; import { describe } from 'mocha'; import { getAllAssignablePermitsForASurveySQL, + getAttachmentsBySurveySQL, + getLatestOccurrenceSubmissionIdSQL, + getLatestSummaryResultIdSQL, + getReportAttachmentsBySurveySQL, getSurveyBasicDataForViewSQL, getSurveyFocalSpeciesDataForViewSQL, getSurveyFundingSourcesDataForViewSQL, @@ -9,12 +13,6 @@ import { } from './survey-view-queries'; describe('getAllAssignablePermitsForASurveySQL', () => { - it('returns null when null project id param provided', () => { - const response = getAllAssignablePermitsForASurveySQL((null as unknown) as number); - - expect(response).to.be.null; - }); - it('returns a non null response when valid params passed in', () => { const response = getAllAssignablePermitsForASurveySQL(1); @@ -31,42 +29,56 @@ describe('getSurveyIdsSQL', () => { }); describe('getSurveyBasicDataForViewSQL', () => { - it('returns a null response when null survey id param provided', () => { - const response = getSurveyBasicDataForViewSQL((null as unknown) as number); + it('returns a non null response when valid params passed in', () => { + const response = getSurveyBasicDataForViewSQL(1); - expect(response).to.be.null; + expect(response).to.not.be.null; }); +}); +describe('getSurveyFundingSourcesDataForViewSQL', () => { it('returns a non null response when valid params passed in', () => { - const response = getSurveyBasicDataForViewSQL(1); + const response = getSurveyFundingSourcesDataForViewSQL(1); expect(response).to.not.be.null; }); }); -describe('getSurveyFundingSourcesDataForViewSQL', () => { - it('returns a null response when null survey id param provided', () => { - const response = getSurveyFundingSourcesDataForViewSQL((null as unknown) as number); +describe('getSurveyFocalSpeciesDataForViewSQL', () => { + it('returns a non null response when valid params passed in', () => { + const response = getSurveyFocalSpeciesDataForViewSQL(1); - expect(response).to.be.null; + expect(response).to.not.be.null; }); +}); +describe('getLatestOccurrenceSubmissionIdSQL', () => { it('returns a non null response when valid params passed in', () => { - const response = getSurveyFundingSourcesDataForViewSQL(1); + const response = getLatestOccurrenceSubmissionIdSQL(1); expect(response).to.not.be.null; }); }); -describe('getSurveyFocalSpeciesDataForViewSQL', () => { - it('returns a null response when null survey id param provided', () => { - const response = getSurveyFocalSpeciesDataForViewSQL((null as unknown) as number); +describe('getLatestSummaryResultIdSQL', () => { + it('returns a non null response when valid params passed in', () => { + const response = getLatestSummaryResultIdSQL(1); + + expect(response).to.not.be.null; + }); +}); + +describe('getAttachmentsBySurveySQL', () => { + it('returns a non null response when valid params passed in', () => { + const response = getAttachmentsBySurveySQL(1); - expect(response).to.be.null; + expect(response).to.not.be.null; }); +}); +describe('getReportAttachmentsBySurveySQL', () => { it('returns a non null response when valid params passed in', () => { - const response = getSurveyFocalSpeciesDataForViewSQL(1); + const response = getReportAttachmentsBySurveySQL(1); expect(response).to.not.be.null; }); diff --git a/api/src/queries/survey/survey-view-queries.ts b/api/src/queries/survey/survey-view-queries.ts index edc3d0bef7..d62f510210 100644 --- a/api/src/queries/survey/survey-view-queries.ts +++ b/api/src/queries/survey/survey-view-queries.ts @@ -9,11 +9,7 @@ import { SQL, SQLStatement } from 'sql-template-strings'; * @param {number} projectId * @returns {SQLStatement} sql query object */ -export const getAllAssignablePermitsForASurveySQL = (projectId: number): SQLStatement | null => { - if (!projectId) { - return null; - } - +export const getAllAssignablePermitsForASurveySQL = (projectId: number): SQLStatement => { return SQL` SELECT number, @@ -44,11 +40,7 @@ export const getSurveyIdsSQL = (projectId: number): SQLStatement => { `; }; -export const getSurveyBasicDataForViewSQL = (surveyId: number): SQLStatement | null => { - if (!surveyId) { - return null; - } - +export const getSurveyBasicDataForViewSQL = (surveyId: number): SQLStatement => { return SQL` SELECT s.survey_id as id, @@ -65,7 +57,6 @@ export const getSurveyBasicDataForViewSQL = (surveyId: number): SQLStatement | n s.location_name, s.geojson as geometry, s.revision_count, - s.publish_timestamp as publish_date, per.number, per.type, max(os.occurrence_submission_id) as occurrence_submission_id, @@ -105,17 +96,12 @@ export const getSurveyBasicDataForViewSQL = (surveyId: number): SQLStatement | n s.location_name, s.geojson, s.revision_count, - s.publish_timestamp, per.number, per.type; `; }; -export const getSurveyFundingSourcesDataForViewSQL = (surveyId: number): SQLStatement | null => { - if (!surveyId) { - return null; - } - +export const getSurveyFundingSourcesDataForViewSQL = (surveyId: number): SQLStatement => { return SQL` SELECT sfs.project_funding_source_id, @@ -162,11 +148,7 @@ export const getSurveyFundingSourcesDataForViewSQL = (surveyId: number): SQLStat `; }; -export const getSurveyFocalSpeciesDataForViewSQL = (surveyId: number): SQLStatement | null => { - if (!surveyId) { - return null; - } - +export const getSurveyFocalSpeciesDataForViewSQL = (surveyId: number): SQLStatement => { return SQL` SELECT wldtaxonomic_units_id, is_focal @@ -179,11 +161,7 @@ export const getSurveyFocalSpeciesDataForViewSQL = (surveyId: number): SQLStatem `; }; -export const getLatestOccurrenceSubmissionIdSQL = (surveyId: number): SQLStatement | null => { - if (!surveyId) { - return null; - } - +export const getLatestOccurrenceSubmissionIdSQL = (surveyId: number): SQLStatement => { return SQL` SELECT max(occurrence_submission_id) as id @@ -194,11 +172,7 @@ export const getLatestOccurrenceSubmissionIdSQL = (surveyId: number): SQLStateme `; }; -export const getLatestSummaryResultIdSQL = (surveyId: number): SQLStatement | null => { - if (!surveyId) { - return null; - } - +export const getLatestSummaryResultIdSQL = (surveyId: number): SQLStatement => { return SQL` SELECT max(survey_summary_submission_id) as id @@ -208,3 +182,56 @@ export const getLatestSummaryResultIdSQL = (surveyId: number): SQLStatement | nu survey_id = ${surveyId}; `; }; + +/** + * SQL query to get survey attachments. + * + * @param {number} surveyId + * @returns {SQLStatement} sql query object + */ +export const getAttachmentsBySurveySQL = (surveyId: number): SQLStatement => { + return SQL` + SELECT + * + FROM + survey_attachment + WHERE + survey_id = ${surveyId}; + `; +}; + +/** + * SQL query to get survey reports. + * + * @param {number} surveyId + * @returns {SQLStatement} sql query object + */ +export const getReportAttachmentsBySurveySQL = (surveyId: number): SQLStatement => { + return SQL` + SELECT + pra.survey_report_attachment_id + , pra.survey_id + , pra.file_name + , pra.title + , pra.description + , pra.year + , pra."key" + , pra.file_size + , pra.security_token + , array_remove(array_agg(pra2.first_name ||' '||pra2.last_name), null) authors + FROM + survey_report_attachment pra + LEFT JOIN survey_report_author pra2 ON pra2.survey_report_attachment_id = pra.survey_report_attachment_id + WHERE pra.survey_id = ${surveyId} + GROUP BY + pra.survey_report_attachment_id + , pra.survey_id + , pra.file_name + , pra.title + , pra.description + , pra.year + , pra."key" + , pra.file_size + , pra.security_token; + `; +}; diff --git a/api/src/queries/users/user-queries.ts b/api/src/queries/users/user-queries.ts index d4148d88c5..0b471e3ae5 100644 --- a/api/src/queries/users/user-queries.ts +++ b/api/src/queries/users/user-queries.ts @@ -1,4 +1,5 @@ import { SQL, SQLStatement } from 'sql-template-strings'; +import { SYSTEM_IDENTITY_SOURCE } from '../../constants/database'; /** * SQL query to get a single user and their system roles, based on their user_identifier. @@ -99,8 +100,12 @@ export const getUserListSQL = (): SQLStatement | null => { system_role sr ON sur.system_role_id = sr.system_role_id + LEFT JOIN + user_identity_source uis + ON + su.user_identity_source_id = uis.user_identity_source_id WHERE - su.record_end_date IS NULL + su.record_end_date IS NULL and uis.name not in (${SYSTEM_IDENTITY_SOURCE.DATABASE}) GROUP BY su.system_user_id, su.record_end_date, diff --git a/api/src/repositories/base-repository.ts b/api/src/repositories/base-repository.ts new file mode 100644 index 0000000000..bde6105710 --- /dev/null +++ b/api/src/repositories/base-repository.ts @@ -0,0 +1,15 @@ +import { IDBConnection } from '../database/db'; + +/** + * Base class for repositories. + * + * @export + * @class BaseRepository + */ +export class BaseRepository { + connection: IDBConnection; + + constructor(connection: IDBConnection) { + this.connection = connection; + } +} diff --git a/api/src/repositories/permit-repository.test.ts b/api/src/repositories/permit-repository.test.ts new file mode 100644 index 0000000000..440d8ad65f --- /dev/null +++ b/api/src/repositories/permit-repository.test.ts @@ -0,0 +1,149 @@ +import chai, { expect } from 'chai'; +import { describe } from 'mocha'; +import { QueryResult } from 'pg'; +import sinon from 'sinon'; +import sinonChai from 'sinon-chai'; +import { getMockDBConnection } from '../__mocks__/db'; +import { IPermitModel, PermitRepository } from './permit-repository'; + +chai.use(sinonChai); + +describe('PermitRepository', () => { + describe('getPermitBySurveyId', () => { + afterEach(() => { + sinon.restore(); + }); + + it('should return an array of survey permits by survey id', async () => { + const mockQueryResponse = ({ + rowCount: 1, + rows: [{ permit_id: 2 }] + } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ + sql: sinon.stub().resolves(mockQueryResponse) + }); + + const permitRepository = new PermitRepository(mockDBConnection); + + const response = await permitRepository.getPermitBySurveyId(1); + + expect(response).to.eql([{ permit_id: 2 }]); + }); + }); + + describe('getPermitByUser', () => { + afterEach(() => { + sinon.restore(); + }); + + it('should return an array of survey permits by user', async () => { + const mockQueryResponse = ({ + rowCount: 1, + rows: [{ permit_id: 2 }] + } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ + sql: sinon.stub().resolves(mockQueryResponse) + }); + + const permitRepository = new PermitRepository(mockDBConnection); + + const response = await permitRepository.getPermitByUser(1); + + expect(response).to.eql([{ permit_id: 2 }]); + }); + }); + + describe('getAllPermits', () => { + afterEach(() => { + sinon.restore(); + }); + + it('should return an array containing all survey permits', async () => { + const mockQueryResponse = ({ + rowCount: 1, + rows: [{ permit_id: 2 }] + } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ + sql: sinon.stub().resolves(mockQueryResponse) + }); + + const permitRepository = new PermitRepository(mockDBConnection); + + const response = await permitRepository.getAllPermits(); + + expect(response).to.eql([{ permit_id: 2 }]); + }); + }); + + describe('updateSurveyPermit', () => { + afterEach(() => { + sinon.restore(); + }); + + it('should return an array of survey permits by user', async () => { + const mockQueryResponse = ({ + rowCount: 1, + rows: [{ permit_id: 2 }] + } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ + sql: sinon.stub().resolves(mockQueryResponse) + }); + + const permitRepository = new PermitRepository(mockDBConnection); + + const response = await permitRepository.updateSurveyPermit(1, 2, '12345', 'permit type'); + + expect(response).to.equal(2); + }); + }); + + describe('createSurveyPermit', () => { + afterEach(() => { + sinon.restore(); + }); + + it('should return an array of survey permits by user', async () => { + const mockQueryResponse = ({ + rowCount: 1, + rows: [{ permit_id: 2 }] + } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ + sql: sinon.stub().resolves(mockQueryResponse) + }); + + const permitRepository = new PermitRepository(mockDBConnection); + + const response = await permitRepository.createSurveyPermit(1, '12345', 'permit type'); + + expect(response).to.equal(2); + }); + }); + + describe('deleteSurveyPermit', () => { + afterEach(() => { + sinon.restore(); + }); + + it('should return an array of survey permits by user', async () => { + const mockQueryResponse = ({ + rowCount: 1, + rows: [{ permit_id: 2 }] + } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ + sql: sinon.stub().resolves(mockQueryResponse) + }); + + const permitRepository = new PermitRepository(mockDBConnection); + + const response = await permitRepository.deleteSurveyPermit(1, 2); + + expect(response).to.equal(2); + }); + }); +}); diff --git a/api/src/repositories/permit-repository.ts b/api/src/repositories/permit-repository.ts new file mode 100644 index 0000000000..2500a3b119 --- /dev/null +++ b/api/src/repositories/permit-repository.ts @@ -0,0 +1,192 @@ +import SQL from 'sql-template-strings'; +import { PROJECT_ROLE } from '../constants/roles'; +import { BaseRepository } from './base-repository'; + +export interface IPermitModel { + permit_id: number; + survey_id: number | null; + number: string; + type: string; + create_date: string; + create_user: number; + update_date: string | null; + update_user: number | null; + revision_count: number; +} + +/** + * A repository class for accessing permit data. + * + * @export + * @class PermitRepository + * @extends {BaseRepository} + */ +export class PermitRepository extends BaseRepository { + /** + * Fetch permit records by survey_id. + * + * @param {number} surveyId + * @return {*} {Promise} + * @memberof PermitRepository + */ + async getPermitBySurveyId(surveyId: number): Promise { + const sqlStatement = SQL` + SELECT + p.* + FROM + permit p + WHERE + p.survey_id = ${surveyId} + ; + `; + + const response = await this.connection.sql(sqlStatement); + + return response.rows; + } + + /** + * Fetch permit records by user. + * + * @param + * @return {*} {Promise} + * @memberof PermitRepository + */ + async getPermitByUser(systemUserId: number): Promise { + const sqlStatement = SQL` + SELECT + p.* + FROM + permit p + , survey s + , project p2 + , project_participation pp + , project_role pr + WHERE + p.survey_id = s.survey_id + AND + s.project_id = p2.project_id + AND + p2.project_id = pp.project_id + AND + pr."name" in ('${PROJECT_ROLE.PROJECT_LEAD}', '${PROJECT_ROLE.PROJECT_EDITOR}') + AND + pp.project_role_id = pr.project_role_id + AND + pp.system_user_id = ${systemUserId}; + `; + + const response = await this.connection.sql(sqlStatement); + + return response.rows; + } + + /** + * Fetch all permit records. + * + * @param + * @return {*} {Promise} + * @memberof PermitRepository + */ + async getAllPermits(): Promise { + const sqlStatement = SQL` + SELECT + p.* + FROM + permit p; + `; + + const response = await this.connection.sql(sqlStatement); + + return response.rows; + } + + /** + * Update survey permit. + * + * @param {number} surveyId + * @param {number} permitId + * @param {string} permitNumber + * @param {string} permitType + * @return {*} number + * @memberof PermitRepository + */ + async updateSurveyPermit( + surveyId: number, + permitId: number, + permitNumber: string, + permitType: string + ): Promise { + const sqlStatement = SQL` + UPDATE permit + SET + "number" = ${permitNumber} + , type = ${permitType} + WHERE + permit_id = ${permitId} + AND + survey_id = ${surveyId} + RETURNING permit_id + ; + `; + + const response = await this.connection.sql(sqlStatement); + + const result = (response && response.rows && response.rows[0]) || null; + + return result.permit_id; + } + + /** + * Create survey permit. + * + * @param {number} surveyId + * @param {string} permitNumber + * @param {string} permitType + * @return {*} number + * @memberof PermitRepository + */ + async createSurveyPermit(surveyId: number, permitNumber: string, permitType: string): Promise { + const sqlStatement = SQL` + INSERT INTO + permit (survey_id, "number", type) + VALUES + (${surveyId}, ${permitNumber}, ${permitType}) + RETURNING permit_id + ; + `; + + const response = await this.connection.sql(sqlStatement); + + const result = (response && response.rows && response.rows[0]) || null; + + return result.permit_id; + } + + /** + * Delete survey permit. + * + * @param {number} surveyId + * @param {number} permitId + * @return {*} number + * @memberof PermitRepository + */ + async deleteSurveyPermit(surveyId: number, permitId: number): Promise { + const sqlStatement = SQL` + DELETE FROM + permit + WHERE + permit_id = ${permitId} + AND + survey_id = ${surveyId} + RETURNING permit_id + ; + `; + + const response = await this.connection.sql(sqlStatement); + + const result = (response && response.rows && response.rows[0]) || null; + + return result.permit_id; + } +} diff --git a/api/src/request-handlers/security/authorization.ts b/api/src/request-handlers/security/authorization.ts index f0f2193c58..2ce4c9437f 100644 --- a/api/src/request-handlers/security/authorization.ts +++ b/api/src/request-handlers/security/authorization.ts @@ -1,5 +1,4 @@ -import { Request } from 'express'; -import { RequestHandler } from 'express-serve-static-core'; +import { Request, RequestHandler } from 'express'; import { PROJECT_ROLE, SYSTEM_ROLE } from '../../constants/roles'; import { getDBConnection, IDBConnection } from '../../database/db'; import { HTTP403, HTTP500 } from '../../errors/custom-error'; diff --git a/api/src/services/eml-service.ts b/api/src/services/eml-service.ts index 23f8ed77ca..db394527b9 100644 --- a/api/src/services/eml-service.ts +++ b/api/src/services/eml-service.ts @@ -5,8 +5,16 @@ import { coordEach } from '@turf/meta'; import jsonpatch from 'fast-json-patch'; import xml2js from 'xml2js'; import { IDBConnection } from '../database/db'; -import { IGetProject } from '../models/project-view'; -import { SurveyObject } from '../models/survey-view'; +import { + GetAttachmentsData as GetProjectAttachmentsData, + GetReportAttachmentsData as GetProjectReportAttachmentsData, + IGetProject +} from '../models/project-view'; +import { + GetAttachmentsData as GetSurveyAttachmentsData, + GetReportAttachmentsData as GetSurveyReportAttachmentsData, + SurveyObject +} from '../models/survey-view'; import { getDbCharacterSystemMetaDataConstantSQL } from '../queries/codes/db-constant-queries'; import { CodeService, IAllCodeSets } from './code-service'; import { ProjectService } from './project-service'; @@ -36,13 +44,20 @@ type EMLDBConstants = { EML_INTELLECTUAL_RIGHTS: string; }; +type SurveyObjectWithAttachments = SurveyObject & { + attachments?: GetSurveyAttachmentsData; + report_attachments?: GetSurveyReportAttachmentsData; +}; + type Cache = { projectData?: IGetProject; - surveyData?: SurveyObject[]; + surveyData?: SurveyObjectWithAttachments[]; + projectAttachmentData?: GetProjectAttachmentsData; + projectReportAttachmentData?: GetProjectReportAttachmentsData; codes?: IAllCodeSets; }; -type BuildProjectEMLOptions = { +export type BuildProjectEMLOptions = { /** * Whether or not to include typically non-public data in the EML. Defaults to `false`. * @@ -108,14 +123,14 @@ export class EmlService extends DBService { /** * Compiles and returns the project metadata as an Ecological Metadata Language (EML) compliant XML string. * - * @param {BuildProjectEMLOptions} options - * @return {*} + * @param {BuildProjectEMLOptions} [options] + * @return {*} {Promise} * @memberof EmlService */ - async buildProjectEml(options: BuildProjectEMLOptions) { - this.includeSensitiveData = options.includeSensitiveData || false; + async buildProjectEml(options?: BuildProjectEMLOptions): Promise { + this.includeSensitiveData = options?.includeSensitiveData || false; - this.surveyIds = options.surveyIds; + this.surveyIds = options?.surveyIds || []; await this.loadProjectData(); await this.loadSurveyData(); @@ -188,13 +203,25 @@ export class EmlService extends DBService { return this.cache.projectData; } + get projectAttachmentData(): GetProjectAttachmentsData | undefined { + return this.cache.projectAttachmentData; + } + + get projectReportAttachmentData(): GetProjectReportAttachmentsData | undefined { + return this.cache.projectReportAttachmentData; + } + async loadProjectData() { const projectData = await this.projectService.getProjectById(this.projectId); + const attachmentData = await this.projectService.getAttachmentsData(this.projectId); + const attachmentReportData = await this.projectService.getReportAttachmentsData(this.projectId); this.cache.projectData = projectData; + this.cache.projectAttachmentData = attachmentData; + this.cache.projectReportAttachmentData = attachmentReportData; } - get surveyData(): SurveyObject[] { + get surveyData(): SurveyObjectWithAttachments[] { if (!this.cache.surveyData) { throw Error('Survey data was not loaded'); } @@ -208,11 +235,19 @@ export class EmlService extends DBService { const allSurveyIds = response.map((item) => item.id); // if `BuildProjectEMLOptions.surveyIds` was provided then filter out any ids not in the list - const includedSurveyIds = allSurveyIds.filter((item) => !this.surveyIds || this.surveyIds?.includes(item)); + const includedSurveyIds = allSurveyIds.filter((item) => !this.surveyIds?.length || this.surveyIds?.includes(item)); const surveyData = await this.surveyService.getSurveysByIds(includedSurveyIds); this.cache.surveyData = surveyData; + + this.cache.surveyData.forEach( + async (item) => (item.attachments = await this.surveyService.getAttachmentsData(item.survey_details.id)) + ); + this.cache.surveyData.forEach( + async (item) => + (item.report_attachments = await this.surveyService.getReportAttachmentsData(item.survey_details.id)) + ); } buildEMLSection() { @@ -251,11 +286,12 @@ export class EmlService extends DBService { $: { system: this.constants.EML_PROVIDER_URL, id: this.packageId }, title: options?.datasetTitle || this.projectData.project.project_name, creator: this.getDatasetCreator(), - ...(this.projectData.project.publish_date && { pubDate: this.projectData.project.publish_date }), metadataProvider: { organizationName: this.constants.EML_ORGANIZATION_NAME, onlineUrl: this.constants.EML_ORGANIZATION_URL }, + //EML specification expects short ISO format + pubDate: new Date().toISOString().substring(0, 10), language: 'English', contact: this.getProjectContact(), project: { @@ -373,37 +409,39 @@ export class EmlService extends DBService { }); } - if (this.includeSensitiveData) { - // only include permits if sensitive data is enabled - if (this.projectData.permit.permits?.length) { - data.push({ - describes: this.projectData.project.uuid, - metadata: { - permits: { - permit: this.projectData.permit.permits.map((item) => { - return { permitType: item.permit_type, permitNumber: item.permit_number }; - }) - } - } - }); - } - } - - if (this.includeSensitiveData) { - // only include permits if sensitive data is enabled - this.surveyData.forEach((item) => { - if (item.permit.permit_number && item.permit.permit_type) { - data.push({ - describes: item.survey_details.uuid, - metadata: { - permits: { - permit: { permitType: item.permit.permit_type, permitNumber: item.permit.permit_number } - } - } - }); - } - }); - } + // TODO add back when survey supports permits + // if (this.includeSensitiveData) { + // // only include permits if sensitive data is enabled + // if (this.projectData.permit.permits?.length) { + // data.push({ + // describes: this.projectData.project.uuid, + // metadata: { + // permits: { + // permit: this.projectData.permit.permits.map((item) => { + // return { permitType: item.permit_type, permitNumber: item.permit_number }; + // }) + // } + // } + // }); + // } + // } + + // TODO add back when survey supports permits + // if (this.includeSensitiveData) { + // // only include permits if sensitive data is enabled + // this.surveyData.forEach((item) => { + // if (item.permit.permit_number && item.permit.permit_type) { + // data.push({ + // describes: item.survey_details.uuid, + // metadata: { + // permits: { + // permit: { permitType: item.permit.permit_type, permitNumber: item.permit.permit_number } + // } + // } + // }); + // } + // }); + // } this.surveyData.forEach((item) => { if (item.proprietor) { @@ -446,6 +484,62 @@ export class EmlService extends DBService { }); }); + if (this.projectAttachmentData?.attachmentDetails.length) { + data.push({ + describes: this.projectData.project.uuid, + metadata: { + projectAttachments: { + projectAttachment: this.projectAttachmentData.attachmentDetails.map((item) => { + return item; + }) + } + } + }); + } + + if (this.projectReportAttachmentData?.attachmentDetails.length) { + data.push({ + describes: this.projectData.project.uuid, + metadata: { + projectReportAttachments: { + projectReportAttachment: this.projectReportAttachmentData.attachmentDetails.map((item) => { + return item; + }) + } + } + }); + } + + this.surveyData.forEach((item) => { + if (item.attachments?.attachmentDetails.length) { + data.push({ + describes: item.survey_details.uuid, + metadata: { + surveyAttachments: { + surveyAttachment: item.attachments?.attachmentDetails.map((item) => { + return item; + }) + } + } + }); + } + }); + + this.surveyData.forEach((item) => { + if (item.report_attachments?.attachmentDetails.length) { + data.push({ + describes: item.survey_details.uuid, + metadata: { + surveyReportAttachments: { + surveyReportAttachment: item.report_attachments?.attachmentDetails.map((item) => { + return item; + }) + } + } + }); + } + }); + jsonpatch.applyOperation(this.data, { op: 'add', path: '/eml:eml/additionalMetadata', @@ -521,11 +615,11 @@ export class EmlService extends DBService { * Get all contacts for the survey. * * @ - * @param {SurveyObject} surveyData + * @param {SurveyObjectWithAttachments} surveyData * @return {*} {Record[]} * @memberof EmlService */ - getSurveyPersonnel(surveyData: SurveyObject): Record[] { + getSurveyPersonnel(surveyData: SurveyObjectWithAttachments): Record[] { return [ { individualName: { @@ -561,7 +655,7 @@ export class EmlService extends DBService { }; } - getSurveyFundingSources(surveyData: SurveyObject): Record { + getSurveyFundingSources(surveyData: SurveyObjectWithAttachments): Record { if (!surveyData.funding.funding_sources.length) { return {}; } @@ -603,7 +697,7 @@ export class EmlService extends DBService { }; } - getSurveyTemporalCoverageEML(surveyData: SurveyObject): Record { + getSurveyTemporalCoverageEML(surveyData: SurveyObjectWithAttachments): Record { if (!surveyData.survey_details.end_date) { // no end date return { @@ -670,7 +764,7 @@ export class EmlService extends DBService { }; } - getSurveyGeographicCoverageEML(surveyData: SurveyObject): Record { + getSurveyGeographicCoverageEML(surveyData: SurveyObjectWithAttachments): Record { if (!surveyData.location.geometry?.length) { return {}; } @@ -719,7 +813,7 @@ export class EmlService extends DBService { }; } - async getSurveyFocalTaxonomicCoverage(surveyData: SurveyObject): Promise> { + async getSurveyFocalTaxonomicCoverage(surveyData: SurveyObjectWithAttachments): Promise> { const taxonomySearchService = new TaxonomyService(); // TODO include ancillary_species alongside focal_species? @@ -741,7 +835,7 @@ export class EmlService extends DBService { return { taxonomicClassification: taxonomicClassifications }; } - async getSurveyDesignDescription(surveyData: SurveyObject): Promise> { + async getSurveyDesignDescription(surveyData: SurveyObjectWithAttachments): Promise> { return { description: { section: [ @@ -784,7 +878,7 @@ export class EmlService extends DBService { return Promise.all(promises); } - async getSurveyEML(surveyData: SurveyObject): Promise> { + async getSurveyEML(surveyData: SurveyObjectWithAttachments): Promise> { return { $: { id: surveyData.survey_details.uuid, system: this.constants.EML_PROVIDER_URL }, title: surveyData.survey_details.survey_name, diff --git a/api/src/services/permit-service.test.ts b/api/src/services/permit-service.test.ts index 5319b43d09..a6f3a8c0ac 100644 --- a/api/src/services/permit-service.test.ts +++ b/api/src/services/permit-service.test.ts @@ -1,313 +1,110 @@ import chai, { expect } from 'chai'; -import { QueryResult } from 'pg'; import sinon from 'sinon'; import sinonChai from 'sinon-chai'; -import SQL from 'sql-template-strings'; -import { HTTPError } from '../errors/custom-error'; -import permit_queries from '../queries/permit'; +import { PermitRepository } from '../repositories/permit-repository'; import { getMockDBConnection } from '../__mocks__/db'; import { PermitService } from './permit-service'; chai.use(sinonChai); describe('PermitService', () => { - describe('getAllPermits', () => { - afterEach(() => { - sinon.restore(); - }); - - it('should throw a 400 error when no sql statement returned for permits', async () => { - const mockDBConnection = getMockDBConnection(); - const systemUserId = 22; - - sinon.stub(permit_queries, 'getAllPermitsSQL').returns(null); - - const permitService = new PermitService(mockDBConnection); - - try { - await permitService.getAllPermits(systemUserId); - - expect.fail(); - } catch (actualError) { - expect((actualError as HTTPError).status).to.equal(400); - expect((actualError as HTTPError).message).to.equal('Failed to build SQL get statement'); - } - }); - - it('should return null when permits response has no rows', async () => { - const mockQueryResponse = (null as unknown) as QueryResult; - const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); - - const systemUserId = 22; + it('constructs', () => { + const mockDBConnection = getMockDBConnection(); - sinon.stub(permit_queries, 'getAllPermitsSQL').returns(SQL`some query`); + const permitService = new PermitService(mockDBConnection); - const permitService = new PermitService(mockDBConnection); - - try { - await permitService.getAllPermits(systemUserId); - - expect.fail(); - } catch (actualError) { - expect((actualError as HTTPError).status).to.equal(400); - expect((actualError as HTTPError).message).to.equal('Failed to get all user permits'); - } - }); - - it('should return all permits on success', async () => { - const allPermits = [ - { - id: 1, - number: '123', - type: 'scientific', - coordinator_agency: 'agency', - project_name: 'project 1' - }, - { - id: 2, - number: '12345', - type: 'wildlife', - coordinator_agency: 'agency 2', - project_name: null - } - ]; - - const mockQueryResponse = ({ rows: allPermits } as unknown) as QueryResult; - const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); - - const systemUserId = 22; - - sinon.stub(permit_queries, 'getAllPermitsSQL').returns(SQL`some query`); - - const permitService = new PermitService(mockDBConnection); - const result = await permitService.getAllPermits(systemUserId); - - expect(result).to.eql(allPermits); - }); + expect(permitService).to.be.instanceof(PermitService); }); - describe('getNonSamplingPermits', () => { - afterEach(() => { - sinon.restore(); - }); - - it('should throw a 400 error when no sql statement returned for non-sampling permits', async () => { + describe('getPermitBySurveyId', () => { + it('fetches permits by survey id', async () => { const mockDBConnection = getMockDBConnection(); - const systemUserId = 22; - - sinon.stub(permit_queries, 'getNonSamplingPermitsSQL').returns(null); - - const permitService = new PermitService(mockDBConnection); - - try { - await permitService.getNonSamplingPermits(systemUserId); - - expect.fail(); - } catch (actualError) { - expect((actualError as HTTPError).status).to.equal(400); - expect((actualError as HTTPError).message).to.equal('Failed to build SQL get statement'); - } - }); - - it('should throw a 400 error when permits response has no rows', async () => { - const mockQueryResponse = (null as unknown) as QueryResult; - const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); - - const systemUserId = 22; - - sinon.stub(permit_queries, 'getNonSamplingPermitsSQL').returns(SQL`some query`); - - const permitService = new PermitService(mockDBConnection); - try { - await permitService.getNonSamplingPermits(systemUserId); - - expect.fail(); - } catch (actualError) { - expect((actualError as HTTPError).status).to.equal(400); - expect((actualError as HTTPError).message).to.equal('Failed to get all user permits'); - } - }); - - it('should return non-sampling permits on success', async () => { - const nonSamplingPermits = [ - { - permit_id: 1, - number: '123', - type: 'scientific' - }, + const mockResponse = [ { permit_id: 2, + survey_id: 1, number: '12345', - type: 'wildlife' + type: 'permit type', + create_date: new Date().toISOString(), + create_user: 3, + update_date: null, + update_user: null, + revision_count: 0 } ]; - const mockQueryResponse = ({ rows: nonSamplingPermits } as unknown) as QueryResult; - const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); - - const systemUserId = 22; - - sinon.stub(permit_queries, 'getNonSamplingPermitsSQL').returns(SQL`some query`); + const getPermitBySurveyIdStub = sinon + .stub(PermitRepository.prototype, 'getPermitBySurveyId') + .resolves(mockResponse); const permitService = new PermitService(mockDBConnection); - const result = await permitService.getNonSamplingPermits(systemUserId); - expect(result).to.eql(nonSamplingPermits); - }); - }); + const response = await permitService.getPermitBySurveyId(1); - describe('createNoSamplePermits', () => { - const sampleReq = { - keycloak_token: {}, - body: { - coordinator: { - first_name: 'first', - last_name: 'last', - email_address: 'email@example.com', - coordinator_agency: 'agency', - share_contact_details: true - }, - permit: { - permits: [ - { - permit_number: 'number', - permit_type: 'type' - } - ] - } - } - } as any; + expect(getPermitBySurveyIdStub).to.have.been.calledOnceWith(1); - afterEach(() => { - sinon.restore(); + expect(response).to.equal(mockResponse); }); + }); - it('should throw a 400 error when no permit passed in request body', async () => { + describe('createSurveyPermit', () => { + it('creates a new surevy permit', async () => { const mockDBConnection = getMockDBConnection(); - const permitService = new PermitService(mockDBConnection); - - try { - await permitService.createNoSamplePermits({ ...sampleReq.body, permit: null }); + const mockResponse = 2; - expect.fail(); - } catch (actualError) { - expect((actualError as HTTPError).status).to.equal(400); - expect((actualError as HTTPError).message).to.equal('Missing request body param `permit`'); - } - }); - - it('should throw a 400 error when no coordinator passed in request body', async () => { - const mockDBConnection = getMockDBConnection(); + const createSurveyPermitStub = sinon + .stub(PermitRepository.prototype, 'createSurveyPermit') + .resolves(mockResponse); const permitService = new PermitService(mockDBConnection); - try { - await permitService.createNoSamplePermits({ ...sampleReq.body, coordinator: null }); + const response = await permitService.createSurveyPermit(1, '12345', 'permit type'); - expect.fail(); - } catch (actualError) { - expect((actualError as HTTPError).status).to.equal(400); - expect((actualError as HTTPError).message).to.equal('Missing request body param `coordinator`'); - } + expect(createSurveyPermitStub).to.have.been.calledOnceWith(1, '12345', 'permit type'); + + expect(response).to.equal(mockResponse); }); + }); - it('should return the inserted ids on success', async () => { + describe('updateSurveyPermit', () => { + it('updates an existing survey permit', async () => { const mockDBConnection = getMockDBConnection(); - const permitService = new PermitService(mockDBConnection); - - sinon.stub(PermitService.prototype, 'insertNoSamplePermit').resolves(20); - - const result = await permitService.createNoSamplePermits(sampleReq.body); + const mockResponse = 2; - expect(result).to.eql([20]); - }); - - it('should throw an error when a failure occurs', async () => { - const expectedError = new Error('cannot process request'); - - const mockDBConnection = getMockDBConnection(); + const updateSurveyPermitStub = sinon + .stub(PermitRepository.prototype, 'updateSurveyPermit') + .resolves(mockResponse); const permitService = new PermitService(mockDBConnection); - sinon.stub(PermitService.prototype, 'insertNoSamplePermit').rejects(expectedError); + const response = await permitService.updateSurveyPermit(1, 2, '12345', 'permit type'); - try { - await permitService.createNoSamplePermits(sampleReq.body); + expect(updateSurveyPermitStub).to.have.been.calledOnceWith(1, 2, '12345', 'permit type'); - expect.fail(); - } catch (actualError) { - expect((actualError as HTTPError).message).to.equal(expectedError.message); - } + expect(response).to.equal(mockResponse); }); }); - describe('insertNoSamplePermit', () => { - afterEach(() => { - sinon.restore(); - }); - - const permitData = { - permit_number: 'number', - permit_type: 'type' - }; - - const coordinatorData = { - first_name: 'first', - last_name: 'last', - email_address: 'email@example.com', - coordinator_agency: 'agency', - share_contact_details: true - }; - - it('should throw an error when cannot generate post sql statement', async () => { + describe('deleteSurveyPermit', () => { + it('deletes an existing survey permit', async () => { const mockDBConnection = getMockDBConnection(); - sinon.stub(permit_queries, 'postPermitNoSamplingSQL').returns(null); - - const permitService = new PermitService(mockDBConnection); - - try { - await permitService.insertNoSamplePermit(permitData, coordinatorData); - - expect.fail(); - } catch (actualError) { - expect((actualError as HTTPError).status).to.equal(400); - expect((actualError as HTTPError).message).to.equal('Failed to build SQL insert statement'); - } - }); + const mockResponse = 2; - it('should throw a HTTP 400 error when failed to insert non-sampling permits cause result is null', async () => { - const mockQueryResponse = (null as unknown) as QueryResult; - const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); - - sinon.stub(permit_queries, 'postPermitNoSamplingSQL').returns(SQL`some`); + const deleteSurveyPermitStub = sinon + .stub(PermitRepository.prototype, 'deleteSurveyPermit') + .resolves(mockResponse); const permitService = new PermitService(mockDBConnection); - try { - await permitService.insertNoSamplePermit(permitData, coordinatorData); - - expect.fail(); - } catch (actualError) { - expect((actualError as HTTPError).status).to.equal(400); - expect((actualError as HTTPError).message).to.equal('Failed to insert non-sampling permit data'); - } - }); - - it('should return the result id on success', async () => { - const mockQueryResponse = ({ rows: [{ id: 12 }] } as unknown) as QueryResult; - const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); - - sinon.stub(permit_queries, 'postPermitNoSamplingSQL').returns(SQL`some`); - - const permitService = new PermitService(mockDBConnection); + const response = await permitService.deleteSurveyPermit(1, 2); - const res = await permitService.insertNoSamplePermit(permitData, coordinatorData); + expect(deleteSurveyPermitStub).to.have.been.calledOnceWith(1, 2); - expect(res).to.equal(12); + expect(response).to.equal(mockResponse); }); }); }); diff --git a/api/src/services/permit-service.ts b/api/src/services/permit-service.ts index abec156a6b..faaa796f49 100644 --- a/api/src/services/permit-service.ts +++ b/api/src/services/permit-service.ts @@ -1,124 +1,95 @@ -import { HTTP400 } from '../errors/custom-error'; -import { IPostPermitNoSampling, PostPermitNoSamplingObject } from '../models/permit-no-sampling'; -import { PostCoordinatorData } from '../models/project-create'; -import { PutCoordinatorData } from '../models/project-update'; -import { queries } from '../queries/queries'; +import { SYSTEM_ROLE } from '../constants/roles'; +import { IDBConnection } from '../database/db'; +import { ApiGeneralError } from '../errors/custom-error'; +import { IPermitModel, PermitRepository } from '../repositories/permit-repository'; import { DBService } from './service'; +import { UserService } from './user-service'; +export class PermitService extends DBService { + permitRepository: PermitRepository; -interface IGetAllPermits { - id: string; - number: string; - type: string; - coordinator_agency: string; - project_name: string; -} + constructor(connection: IDBConnection) { + super(connection); -interface IGetNonSamplingPermits { - permit_id: string; - number: string; - type: string; -} + this.permitRepository = new PermitRepository(connection); + } -export class PermitService extends DBService { /** - * get all non-sampling permits + * Get permit by id. * - * @param {(number | null)} systemUserId - * @return {*} {Promise} + * @param {number} surveyId + * @return {*} {IPermitModel[]} * @memberof PermitService */ - async getAllPermits(systemUserId: number | null): Promise { - const sqlStatement = queries.permit.getAllPermitsSQL(systemUserId); - - if (!sqlStatement) { - throw new HTTP400('Failed to build SQL get statement'); - } - - const response = await this.connection.query(sqlStatement.text, sqlStatement.values); - - if (!response || !response.rows) { - throw new HTTP400('Failed to get all user permits'); - } - - return response.rows; + async getPermitBySurveyId(surveyId: number): Promise { + return this.permitRepository.getPermitBySurveyId(surveyId); } /** - * get all non-sampling permits + * Get permit by user. * - * @param {(number | null)} systemUserId - * @return {*} {Promise} + * @param + * @return {*} {IPermitModel[]} * @memberof PermitService */ - async getNonSamplingPermits(systemUserId: number | null): Promise { - const sqlStatement = queries.permit.getNonSamplingPermitsSQL(systemUserId); + async getPermitByUser(systemUserId: number): Promise { + const userService = new UserService(this.connection); + const user = await userService.getUserById(systemUserId); - if (!sqlStatement) { - throw new HTTP400('Failed to build SQL get statement'); + if (!user) { + throw new ApiGeneralError('Failed to acquire user'); } - const response = await this.connection.query(sqlStatement.text, sqlStatement.values); - - if (!response || !response.rows) { - throw new HTTP400('Failed to get all user permits'); + if ( + user.role_names.includes(SYSTEM_ROLE.SYSTEM_ADMIN) || + user.role_names.includes(SYSTEM_ROLE.DATA_ADMINISTRATOR) + ) { + return this.permitRepository.getAllPermits(); } - return response.rows; + return this.permitRepository.getPermitByUser(systemUserId); } /** - * Creates new no sample permit objects and insert all + * Create and associate permit for survey. * - * @param {object} permitRequestBody - * @return {*} {Promise} + * @param {number} surveyId + * @param {string} permitNumber + * @param {string} permitType + * @return {*} {IPermitModel[]} * @memberof PermitService */ - async createNoSamplePermits(permitRequestBody: object): Promise { - const sanitizedNoSamplePermitPostData = new PostPermitNoSamplingObject(permitRequestBody); - - if (!sanitizedNoSamplePermitPostData.permit || !sanitizedNoSamplePermitPostData.permit.permits.length) { - throw new HTTP400('Missing request body param `permit`'); - } - - if (!sanitizedNoSamplePermitPostData.coordinator) { - throw new HTTP400('Missing request body param `coordinator`'); - } - - return Promise.all( - sanitizedNoSamplePermitPostData.permit.permits.map(async (permit: IPostPermitNoSampling) => - this.insertNoSamplePermit(permit, sanitizedNoSamplePermitPostData.coordinator) - ) - ); + async createSurveyPermit(surveyId: number, permitNumber: string, permitType: string): Promise { + return this.permitRepository.createSurveyPermit(surveyId, permitNumber, permitType); } /** - * insert a no sample permit row. + * Update a survey permit. * - * @param {IPostPermitNoSampling} permit - * @param {(PostCoordinatorData | PutCoordinatorData)} coordinator - * @return {*} {Promise} + * @param {number} surveyId + * @param {number} permitId + * @param {string} permitNumber + * @param {string} permitType + * @return {*} {IPermitModel[]} * @memberof PermitService */ - async insertNoSamplePermit( - permit: IPostPermitNoSampling, - coordinator: PostCoordinatorData | PutCoordinatorData + async updateSurveyPermit( + surveyId: number, + permitId: number, + permitNumber: string, + permitType: string ): Promise { - const systemUserId = this.connection.systemUserId(); - - const sqlStatement = queries.permit.postPermitNoSamplingSQL({ ...permit, ...coordinator }, systemUserId); - - if (!sqlStatement) { - throw new HTTP400('Failed to build SQL insert statement'); - } - - const response = await this.connection.query(sqlStatement.text, sqlStatement.values); - - const result = (response && response.rows && response.rows[0]) || null; - - if (!result || !result.id) { - throw new HTTP400('Failed to insert non-sampling permit data'); - } + return this.permitRepository.updateSurveyPermit(surveyId, permitId, permitNumber, permitType); + } - return result.id; + /** + * Delete a survey permit. + * + * @param {number} surveyId + * @param {number} permitId + * @return {*} QueryResult + * @memberof PermitService + */ + async deleteSurveyPermit(surveyId: number, permitId: number): Promise { + return this.permitRepository.deleteSurveyPermit(surveyId, permitId); } } diff --git a/api/src/services/platform-service.test.ts b/api/src/services/platform-service.test.ts index a483279598..34ca3e7476 100644 --- a/api/src/services/platform-service.test.ts +++ b/api/src/services/platform-service.test.ts @@ -3,19 +3,93 @@ import chai, { expect } from 'chai'; import { describe } from 'mocha'; import sinon from 'sinon'; import sinonChai from 'sinon-chai'; +import { getMockDBConnection } from '../__mocks__/db'; +import { EmlService } from './eml-service'; import { KeycloakService } from './keycloak-service'; import { IDwCADataset, PlatformService } from './platform-service'; chai.use(sinonChai); describe('PlatformService', () => { - describe('submitNewDataPackage', () => { + describe('submitDwCAMetadataPackage', () => { + afterEach(() => { + sinon.restore(); + }); + + it('fetches project EML and submits to the backbone', async () => { + const mockDBConnection = getMockDBConnection(); + + process.env.BACKBONE_INTAKE_ENABLED = 'true'; + + const buildProjectEmlStub = sinon.stub(EmlService.prototype, 'buildProjectEml').resolves('xml data'); + + sinon.stub(EmlService.prototype, 'packageId').get(() => '123-456-789'); + + const _submitDwCADatasetToBioHubBackboneStub = sinon + .stub(PlatformService.prototype, '_submitDwCADatasetToBioHubBackbone') + .resolves({ data_package_id: '123-456-789' }); + + const platformService = new PlatformService(mockDBConnection); + + await platformService.submitDwCAMetadataPackage(1); + + expect(buildProjectEmlStub).to.have.been.calledOnce; + expect(_submitDwCADatasetToBioHubBackboneStub).to.have.been.calledOnceWith({ + archiveFile: { + data: sinon.match.any, + fileName: 'DwCA.zip', + mimeType: 'application/zip' + }, + dataPackageId: '123-456-789' + }); + }); + }); + + describe('submitDwCADataPackage', () => { + afterEach(() => { + sinon.restore(); + }); + + it('fetches project EML and occurrence data and submits to the backbone', async () => { + const mockDBConnection = getMockDBConnection(); + + process.env.BACKBONE_INTAKE_ENABLED = 'true'; + + const buildProjectEmlStub = sinon.stub(EmlService.prototype, 'buildProjectEml').resolves('xml data'); + + sinon.stub(EmlService.prototype, 'packageId').get(() => '123-456-789'); + + const _submitDwCADatasetToBioHubBackboneStub = sinon + .stub(PlatformService.prototype, '_submitDwCADatasetToBioHubBackbone') + .resolves({ data_package_id: '123-456-789' }); + + const platformService = new PlatformService(mockDBConnection); + + await platformService.submitDwCADataPackage(1); + + expect(buildProjectEmlStub).to.have.been.calledOnce; + expect(_submitDwCADatasetToBioHubBackboneStub).to.have.been.calledOnceWith({ + archiveFile: { + data: sinon.match.any, + fileName: 'DwCA.zip', + mimeType: 'application/zip' + }, + dataPackageId: '123-456-789' + }); + }); + }); + + describe('_submitDwCADatasetToBioHubBackbone', () => { afterEach(() => { sinon.restore(); }); it('makes an axios post to the BioHub Platform Backbone API', async () => { - process.env.BACKBONE_API_HOST = 'backbone.com'; + const mockDBConnection = getMockDBConnection(); + + process.env.BACKBONE_API_HOST = 'http://backbone.com'; + process.env.BACKBONE_INTAKE_PATH = 'api/intake'; + process.env.BACKBONE_INTAKE_ENABLED = 'true'; const keycloakServiceStub = sinon.stub(KeycloakService.prototype, 'getKeycloakToken').resolves('token'); @@ -30,22 +104,18 @@ describe('PlatformService', () => { dataPackageId: '123-456-789' }; - const platformService = new PlatformService(); + const platformService = new PlatformService(mockDBConnection); - await platformService.submitNewDataPackage(dwcaDataset); + await platformService._submitDwCADatasetToBioHubBackbone(dwcaDataset); expect(keycloakServiceStub).to.have.been.calledOnce; - expect(axiosStub).to.have.been.calledOnceWith( - 'backbone.com/api/dwc/submission/create', - sinon.match.instanceOf(Buffer), - { - headers: { - authorization: `Bearer token`, - 'content-type': sinon.match(new RegExp(/^multipart\/form-data; boundary=[-]*[0-9]*$/)) - } + expect(axiosStub).to.have.been.calledOnceWith('http://backbone.com/api/intake', sinon.match.instanceOf(Buffer), { + headers: { + authorization: `Bearer token`, + 'content-type': sinon.match(new RegExp(/^multipart\/form-data; boundary=[-]*[0-9]*$/)) } - ); + }); }); }); }); diff --git a/api/src/services/platform-service.ts b/api/src/services/platform-service.ts index c28fbeb6f8..4e9b8e22d2 100644 --- a/api/src/services/platform-service.ts +++ b/api/src/services/platform-service.ts @@ -1,6 +1,13 @@ +import AdmZip from 'adm-zip'; import axios from 'axios'; import FormData from 'form-data'; +import { URL } from 'url'; +import { HTTP400 } from '../errors/custom-error'; +import { getFileFromS3 } from '../utils/file-utils'; +import { EmlService } from './eml-service'; import { KeycloakService } from './keycloak-service'; +import { DBService } from './service'; +import { SurveyService } from './survey-service'; export interface IDwCADataset { archiveFile: { @@ -8,7 +15,13 @@ export interface IDwCADataset { * A Darwin Core Archive (DwCA) zip file. */ data: Buffer; + /** + * The name of the archive file. + */ fileName: string; + /** + * The mime type, should be `application/zip` or similar. + */ mimeType: string; }; /** @@ -17,9 +30,83 @@ export interface IDwCADataset { dataPackageId: string; } -export class PlatformService { +export class PlatformService extends DBService { + BACKBONE_INTAKE_ENABLED = process.env.BACKBONE_INTAKE_ENABLED === 'true' || false; BACKBONE_API_HOST = process.env.BACKBONE_API_HOST; - BACKBONE_API_INGEST_PATH = '/api/dwc/submission/create'; + BACKBONE_INTAKE_PATH = process.env.BACKBONE_INTAKE_PATH || '/api/dwc/submission/intake'; + + /** + * Submit a Darwin Core Archive (DwCA) data package, that only contains the project/survey metadata, to the BioHub + * Platform Backbone. + * + * Why submit only metadata? It is beneficial to submit the metadata as early as possible, so that the project/survey + * is discoverable by users of BioHub, even if the project/survey has not yet completed or not all inventory data has + * been submitted. + * + * Note: Does nothing if `process.env.BACKBONE_INTAKE_ENABLED` is not `true`. + * + * @param {number} projectId + * @return {*} + * @memberof PlatformService + */ + async submitDwCAMetadataPackage(projectId: number) { + if (!this.BACKBONE_INTAKE_ENABLED) { + return; + } + + const emlService = new EmlService({ projectId: projectId }, this.connection); + + const emlString = await emlService.buildProjectEml(); + + const dwcArchiveZip = new AdmZip(); + dwcArchiveZip.addFile('eml.xml', Buffer.from(emlString)); + + const dwCADataset = { + archiveFile: { + data: dwcArchiveZip.toBuffer(), + fileName: 'DwCA.zip', + mimeType: 'application/zip' + }, + dataPackageId: emlService.packageId + }; + + return this._submitDwCADatasetToBioHubBackbone(dwCADataset); + } + + /** + * Submit a Darwin Core Archive (DwCA) data package, that contains both project/survey metadata and survey occurrence + * data, to the BioHub Platform Backbone. + * + * Note: Does nothing if `process.env.BACKBONE_INTAKE_ENABLED` is not `true`. + * + * @param {number} projectId + * @return {*} + * @memberof PlatformService + */ + async submitDwCADataPackage(projectId: number) { + if (!this.BACKBONE_INTAKE_ENABLED) { + return; + } + + const emlService = new EmlService({ projectId: projectId }, this.connection); + + const emlString = await emlService.buildProjectEml(); + + const dwcArchiveZip = new AdmZip(); + dwcArchiveZip.addFile('eml.xml', Buffer.from(emlString)); + // TODO fetch and add DwCA data files to archive + + const dwCADataset = { + archiveFile: { + data: dwcArchiveZip.toBuffer(), + fileName: 'DwCA.zip', + mimeType: 'application/zip' + }, + dataPackageId: emlService.packageId + }; + + return this._submitDwCADatasetToBioHubBackbone(dwCADataset); + } /** * Submit a new Darwin Core Archive (DwCA) data package to the BioHub Platform Backbone. @@ -28,7 +115,7 @@ export class PlatformService { * @return {*} {Promise<{ data_package_id: string }>} * @memberof PlatformService */ - async submitNewDataPackage(dwcaDataset: IDwCADataset): Promise<{ data_package_id: string }> { + async _submitDwCADatasetToBioHubBackbone(dwcaDataset: IDwCADataset): Promise<{ data_package_id: string }> { const keycloakService = new KeycloakService(); const token = await keycloakService.getKeycloakToken(); @@ -42,17 +129,62 @@ export class PlatformService { formData.append('data_package_id', dwcaDataset.dataPackageId); - const { data } = await axios.post<{ data_package_id: string }>( - `${this.BACKBONE_API_HOST}${this.BACKBONE_API_INGEST_PATH}`, - formData.getBuffer(), - { - headers: { - authorization: `Bearer ${token}`, - ...formData.getHeaders() - } + const backboneIntakeUrl = new URL(this.BACKBONE_INTAKE_PATH, this.BACKBONE_API_HOST).href; + + const { data } = await axios.post<{ data_package_id: string }>(backboneIntakeUrl, formData.getBuffer(), { + headers: { + authorization: `Bearer ${token}`, + ...formData.getHeaders() } - ); + }); return data; } + + /** + * Upload Survey/Project/Observation data to Backbone + * + * @param {number} projectId + * @param {number} surveyId + * @return {*} + * @memberof PlatformService + */ + async uploadSurveyDataToBioHub(projectId: number, surveyId: number) { + if (!this.BACKBONE_INTAKE_ENABLED) { + return; + } + + const surveyService = new SurveyService(this.connection); + const surveyData = await surveyService.getLatestSurveyOccurrenceSubmission(surveyId); + + if (!surveyData.output_key) { + throw new HTTP400('no s3Key found'); + } + const s3File = await getFileFromS3(surveyData.output_key); + + if (!s3File) { + throw new HTTP400('no s3File found'); + } + const dwcArchiveZip = new AdmZip(s3File.Body as Buffer); + + const emlService = new EmlService({ projectId: projectId }, this.connection); + const emlString = await emlService.buildProjectEml(); + + if (!emlString) { + throw new HTTP400('emlString failed to build'); + } + + dwcArchiveZip.addFile('eml.xml', Buffer.from(emlString)); + + const dwCADataset = { + archiveFile: { + data: dwcArchiveZip.toBuffer(), + fileName: 'DwCA.zip', + mimeType: 'application/zip' + }, + dataPackageId: emlService.packageId + }; + + return this._submitDwCADatasetToBioHubBackbone(dwCADataset); + } } diff --git a/api/src/services/project-service.test.ts b/api/src/services/project-service.test.ts index db5f7b8a6f..f91d223a40 100644 --- a/api/src/services/project-service.test.ts +++ b/api/src/services/project-service.test.ts @@ -12,7 +12,6 @@ import { GetLocationData, GetObjectivesData, GetPartnershipsData, - GetPermitData, GetProjectData } from '../models/project-view'; import { queries } from '../queries/queries'; @@ -338,7 +337,6 @@ describe('ProjectService', () => { start_date: '1900-01-01', end_date: '2000-10-10', coordinator_agency: 'Agency 1', - permits_list: '3, 100', project_type: 'Aquatic Habitat' }, { @@ -347,7 +345,6 @@ describe('ProjectService', () => { start_date: '1900-01-01', end_date: '2000-12-31', coordinator_agency: 'Agency 2', - permits_list: '1, 4', project_type: 'Terrestrial Habitat' } ]; @@ -392,7 +389,7 @@ describe('ProjectService', () => { }); it('returns empty array if there are no rows', async () => { - const mockQueryResponse = ({ rows: [] } as unknown) as QueryResult; + const mockQueryResponse = ({} as unknown) as QueryResult; const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); sinon.stub(queries.project, 'getProjectListSQL').returns(SQL`valid sql`); @@ -412,8 +409,6 @@ describe('ProjectService', () => { start_date: '1900-01-01', end_date: '2200-10-10', coordinator_agency: 'Agency 1', - publish_timestamp: '2010-01-01', - permits_list: '3, 100', project_type: 'Aquatic Habitat' }, { @@ -422,8 +417,6 @@ describe('ProjectService', () => { start_date: '1900-01-01', end_date: '2000-12-31', coordinator_agency: 'Agency 2', - publish_timestamp: '', - permits_list: '1, 4', project_type: 'Terrestrial Habitat' } ]; @@ -439,12 +432,10 @@ describe('ProjectService', () => { expect(result[0].id).to.equal(123); expect(result[0].name).to.equal('Project 1'); expect(result[0].completion_status).to.equal('Active'); - expect(result[0].publish_status).to.equal('Published'); expect(result[1].id).to.equal(456); expect(result[1].name).to.equal('Project 2'); expect(result[1].completion_status).to.equal('Completed'); - expect(result[1].publish_status).to.equal('Unpublished'); }); }); @@ -494,7 +485,6 @@ describe('ProjectService', () => { sinon.stub(ProjectService.prototype, 'getPublicProjectData').resolves(new GetProjectData()); sinon.stub(ProjectService.prototype, 'getObjectivesData').resolves(new GetObjectivesData()); sinon.stub(ProjectService.prototype, 'getCoordinatorData').resolves(new GetCoordinatorData()); - sinon.stub(ProjectService.prototype, 'getPermitData').resolves(new GetPermitData()); sinon.stub(ProjectService.prototype, 'getLocationData').resolves(new GetLocationData()); sinon.stub(ProjectService.prototype, 'getPartnershipsData').resolves(new GetPartnershipsData()); sinon.stub(ProjectService.prototype, 'getIUCNClassificationData').resolves(new GetIUCNClassificationData()); @@ -507,3 +497,250 @@ describe('ProjectService', () => { expect(result.id).to.equal(1); }); }); + +describe('getProjectData', () => { + afterEach(() => { + sinon.restore(); + }); + + it('returns data if valid return', async () => { + const mockQueryResponse = ({ rows: [{ id: 1 }], rowCount: 0 } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const projectService = new ProjectService(mockDBConnection); + + const response = await projectService.getProjectData(1); + + expect(response).to.eql(new GetProjectData({ id: 1 }, [{ id: 1 }])); + }); + + it('returns null if response is empty', async () => { + const mockQueryResponse = ({ rowCount: 0 } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const projectService = new ProjectService(mockDBConnection); + + try { + await projectService.getProjectData(1); + expect.fail(); + } catch (actualError) { + expect((actualError as HTTPError).message).to.equal('Failed to get project data'); + expect((actualError as HTTPError).status).to.equal(400); + } + }); +}); + +describe('getObjectivesData', () => { + afterEach(() => { + sinon.restore(); + }); + + it('returns data if valid return', async () => { + const mockQueryResponse = ({ rows: [{ id: 1 }], rowCount: 0 } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const projectService = new ProjectService(mockDBConnection); + + const response = await projectService.getObjectivesData(1); + + expect(response).to.eql(new GetObjectivesData({ id: 1 })); + }); + + it('returns null if response is empty', async () => { + const mockQueryResponse = ({ rowCount: 0 } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const projectService = new ProjectService(mockDBConnection); + + try { + await projectService.getObjectivesData(1); + expect.fail(); + } catch (actualError) { + expect((actualError as HTTPError).message).to.equal('Failed to get project objectives data'); + expect((actualError as HTTPError).status).to.equal(400); + } + }); +}); + +describe('getCoordinatorData', () => { + afterEach(() => { + sinon.restore(); + }); + + it('returns data if valid return', async () => { + const mockQueryResponse = ({ rows: [{ id: 1 }], rowCount: 0 } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const projectService = new ProjectService(mockDBConnection); + + const response = await projectService.getCoordinatorData(1); + + expect(response).to.eql(new GetCoordinatorData({ id: 1 })); + }); + + it('returns null if response is empty', async () => { + const mockQueryResponse = ({ rowCount: 0 } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const projectService = new ProjectService(mockDBConnection); + + try { + await projectService.getCoordinatorData(1); + expect.fail(); + } catch (actualError) { + expect((actualError as HTTPError).message).to.equal('Failed to get project contact data'); + expect((actualError as HTTPError).status).to.equal(400); + } + }); +}); + +describe('getLocationData', () => { + afterEach(() => { + sinon.restore(); + }); + + it('returns data if valid return', async () => { + const mockQueryResponse = ({ rows: [{ id: 1 }], rowCount: 0 } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const projectService = new ProjectService(mockDBConnection); + + const response = await projectService.getLocationData(1); + + expect(response).to.eql(new GetLocationData([{ id: 1 }])); + }); + + it('returns null if response is empty', async () => { + const mockQueryResponse = ({ rowCount: 0 } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const projectService = new ProjectService(mockDBConnection); + + try { + await projectService.getLocationData(1); + expect.fail(); + } catch (actualError) { + expect((actualError as HTTPError).message).to.equal('Failed to get project data'); + expect((actualError as HTTPError).status).to.equal(400); + } + }); +}); + +describe('getIUCNClassificationData', () => { + afterEach(() => { + sinon.restore(); + }); + + it('returns data if valid return', async () => { + const mockQueryResponse = ({ rows: [{ id: 1 }], rowCount: 0 } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const projectService = new ProjectService(mockDBConnection); + + const response = await projectService.getIUCNClassificationData(1); + + expect(response).to.eql(new GetIUCNClassificationData([{ id: 1 }])); + }); + + it('returns null if response is empty', async () => { + const mockQueryResponse = ({ rowCount: 0 } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const projectService = new ProjectService(mockDBConnection); + + try { + await projectService.getIUCNClassificationData(1); + expect.fail(); + } catch (actualError) { + expect((actualError as HTTPError).message).to.equal('Failed to get project data'); + expect((actualError as HTTPError).status).to.equal(400); + } + }); +}); + +describe('getFundingData', () => { + afterEach(() => { + sinon.restore(); + }); + + it('returns data if valid return', async () => { + const mockQueryResponse = ({ rows: [{ id: 1 }], rowCount: 0 } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const projectService = new ProjectService(mockDBConnection); + + const response = await projectService.getFundingData(1); + + expect(response).to.eql(new GetFundingData([{ id: 1 }])); + }); + + it('returns null if response is empty', async () => { + const mockQueryResponse = ({} as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const projectService = new ProjectService(mockDBConnection); + + try { + await projectService.getFundingData(1); + expect.fail(); + } catch (actualError) { + expect((actualError as HTTPError).message).to.equal('Failed to get project data'); + expect((actualError as HTTPError).status).to.equal(400); + } + }); +}); + +describe('getPartnershipsData', () => { + afterEach(() => { + sinon.restore(); + }); + + it('returns data if valid return', async () => { + sinon.stub(ProjectService.prototype, 'getIndigenousPartnershipsRows').resolves([]); + sinon.stub(ProjectService.prototype, 'getStakeholderPartnershipsRows').resolves([]); + + const mockQueryResponse = ({ rows: [{ id: 1 }], rowCount: 0 } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const projectService = new ProjectService(mockDBConnection); + + const response = await projectService.getPartnershipsData(1); + + expect(response).to.eql(new GetPartnershipsData([], [])); + }); + + it('throws error if indigenous partnership is empty', async () => { + sinon.stub(ProjectService.prototype, 'getIndigenousPartnershipsRows').resolves(undefined); + sinon.stub(ProjectService.prototype, 'getStakeholderPartnershipsRows').resolves([]); + const mockQueryResponse = ({} as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const projectService = new ProjectService(mockDBConnection); + + try { + await projectService.getPartnershipsData(1); + expect.fail(); + } catch (actualError) { + expect((actualError as HTTPError).message).to.equal('Failed to get indigenous partnership data'); + expect((actualError as HTTPError).status).to.equal(400); + } + }); + + it('throws error if stakeholder partnership is empty', async () => { + sinon.stub(ProjectService.prototype, 'getIndigenousPartnershipsRows').resolves([]); + sinon.stub(ProjectService.prototype, 'getStakeholderPartnershipsRows').resolves(undefined); + + const mockQueryResponse = ({} as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const projectService = new ProjectService(mockDBConnection); + + try { + await projectService.getPartnershipsData(1); + expect.fail(); + } catch (actualError) { + expect((actualError as HTTPError).message).to.equal('Failed to get stakeholder partnership data'); + expect((actualError as HTTPError).status).to.equal(400); + } + }); +}); diff --git a/api/src/services/project-service.ts b/api/src/services/project-service.ts index 0e42c6bf9a..4eb788723a 100644 --- a/api/src/services/project-service.ts +++ b/api/src/services/project-service.ts @@ -1,15 +1,8 @@ import moment from 'moment'; -import { PROJECT_ROLE, SYSTEM_ROLE } from '../constants/roles'; +import { PROJECT_ROLE } from '../constants/roles'; import { COMPLETION_STATUS } from '../constants/status'; -import { HTTP400, HTTP409, HTTP500 } from '../errors/custom-error'; -import { - IPostExistingPermit, - IPostIUCN, - IPostPermit, - PostFundingSource, - PostPermitData, - PostProjectObject -} from '../models/project-create'; +import { HTTP400, HTTP409 } from '../errors/custom-error'; +import { IPostIUCN, PostFundingSource, PostProjectObject } from '../models/project-create'; import { IPutIUCN, PutCoordinatorData, @@ -21,20 +14,20 @@ import { PutProjectData } from '../models/project-update'; import { + GetAttachmentsData, GetCoordinatorData, GetFundingData, GetIUCNClassificationData, GetLocationData, GetObjectivesData, GetPartnershipsData, - GetPermitData, GetProjectData, + GetReportAttachmentsData, IGetProject } from '../models/project-view'; import { getSurveyAttachmentS3Keys } from '../paths/project/{projectId}/survey/{surveyId}/delete'; import { GET_ENTITIES, IUpdateProject } from '../paths/project/{projectId}/update'; import { queries } from '../queries/queries'; -import { userHasValidRole } from '../request-handlers/security/authorization'; import { deleteFileFromS3 } from '../utils/file-utils'; import { DBService } from './service'; @@ -165,8 +158,7 @@ export class ProjectService extends DBService { completion_status: (row.end_date && moment(row.end_date).endOf('day').isBefore(moment()) && COMPLETION_STATUS.COMPLETED) || COMPLETION_STATUS.ACTIVE, - project_type: row.project_type, - permits_list: row.permits_list + project_type: row.project_type })); } @@ -175,7 +167,6 @@ export class ProjectService extends DBService { projectData, objectiveData, coordinatorData, - permitData, locationData, iucnData, fundingData, @@ -184,7 +175,6 @@ export class ProjectService extends DBService { this.getPublicProjectData(projectId), this.getObjectivesData(projectId), this.getCoordinatorData(projectId), - this.getPermitData(projectId), this.getLocationData(projectId), this.getIUCNClassificationData(projectId), this.getFundingData(projectId), @@ -196,7 +186,6 @@ export class ProjectService extends DBService { project: projectData, objectives: objectiveData, coordinator: coordinatorData, - permit: permitData, location: locationData, iucn: iucnData, funding: fundingData, @@ -223,12 +212,10 @@ export class ProjectService extends DBService { start_date: row.start_date, end_date: row.end_date, coordinator_agency: row.coordinator_agency_name, - publish_status: row.publish_timestamp ? 'Published' : 'Unpublished', completion_status: (row.end_date && moment(row.end_date).endOf('day').isBefore(moment()) && COMPLETION_STATUS.COMPLETED) || COMPLETION_STATUS.ACTIVE, - project_type: row.project_type, - permits_list: row.permits_list + project_type: row.project_type })); } @@ -237,7 +224,6 @@ export class ProjectService extends DBService { projectData, objectiveData, coordinatorData, - permitData, locationData, iucnData, fundingData, @@ -246,7 +232,6 @@ export class ProjectService extends DBService { this.getProjectData(projectId), this.getObjectivesData(projectId), this.getCoordinatorData(projectId), - this.getPermitData(projectId), this.getLocationData(projectId), this.getIUCNClassificationData(projectId), this.getFundingData(projectId), @@ -258,7 +243,6 @@ export class ProjectService extends DBService { project: projectData, objectives: objectiveData, coordinator: coordinatorData, - permit: permitData, location: locationData, iucn: iucnData, funding: fundingData, @@ -273,7 +257,6 @@ export class ProjectService extends DBService { const results: Pick & Partial> = { id: projectId, coordinator: undefined, - permit: undefined, project: undefined, objectives: undefined, location: undefined, @@ -292,14 +275,6 @@ export class ProjectService extends DBService { ); } - if (entities.includes(GET_ENTITIES.permit)) { - promises.push( - this.getPermitData(projectId).then((value) => { - results.permit = value; - }) - ); - } - if (entities.includes(GET_ENTITIES.partnerships)) { promises.push( this.getPartnershipsData(projectId).then((value) => { @@ -356,10 +331,6 @@ export class ProjectService extends DBService { const getProjectSqlStatement = queries.project.getProjectSQL(projectId); const getProjectActivitiesSQLStatement = queries.project.getActivitiesByProjectSQL(projectId); - if (!getProjectSqlStatement || !getProjectActivitiesSQLStatement) { - throw new HTTP400('Failed to build SQL get statement'); - } - const [project, activity] = await Promise.all([ this.connection.query(getProjectSqlStatement.text, getProjectSqlStatement.values), this.connection.query(getProjectActivitiesSQLStatement.text, getProjectActivitiesSQLStatement.values) @@ -378,12 +349,7 @@ export class ProjectService extends DBService { async getObjectivesData(projectId: number): Promise { const sqlStatement = queries.project.getObjectivesByProjectSQL(projectId); - if (!sqlStatement) { - throw new HTTP400('Failed to build SQL get statement'); - } - const response = await this.connection.query(sqlStatement.text, sqlStatement.values); - const result = (response && response.rows && response.rows[0]) || null; if (!result) { @@ -396,12 +362,7 @@ export class ProjectService extends DBService { async getCoordinatorData(projectId: number): Promise { const sqlStatement = queries.project.getCoordinatorByProjectSQL(projectId); - if (!sqlStatement) { - throw new HTTP400('Failed to build SQL get statement'); - } - const response = await this.connection.query(sqlStatement.text, sqlStatement.values); - const result = (response && response.rows && response.rows[0]) || null; if (!result) { @@ -411,31 +372,9 @@ export class ProjectService extends DBService { return new GetCoordinatorData(result); } - async getPermitData(projectId: number): Promise { - const sqlStatement = queries.project.getProjectPermitsSQL(projectId); - - if (!sqlStatement) { - throw new HTTP400('Failed to build SQL select statement'); - } - - const response = await this.connection.query(sqlStatement.text, sqlStatement.values); - - const result = (response && response.rows) || null; - - if (!result) { - throw new HTTP400('Failed to get project permit data'); - } - - return new GetPermitData(result); - } - async getLocationData(projectId: number): Promise { const sqlStatement = queries.project.getLocationByProjectSQL(projectId); - if (!sqlStatement) { - throw new HTTP400('Failed to build SQL get statement'); - } - const response = await this.connection.query(sqlStatement.text, sqlStatement.values); const result = (response && response.rows) || null; @@ -450,10 +389,6 @@ export class ProjectService extends DBService { async getIUCNClassificationData(projectId: number): Promise { const sqlStatement = queries.project.getIUCNActionClassificationByProjectSQL(projectId); - if (!sqlStatement) { - throw new HTTP400('Failed to build SQL get statement'); - } - const response = await this.connection.query(sqlStatement.text, sqlStatement.values); const result = (response && response.rows) || null; @@ -468,10 +403,6 @@ export class ProjectService extends DBService { async getFundingData(projectId: number): Promise { const sqlStatement = queries.project.getFundingSourceByProjectSQL(projectId); - if (!sqlStatement) { - throw new HTTP400('Failed to build SQL get statement'); - } - const response = await this.connection.query(sqlStatement.text, sqlStatement.values); const result = (response && response.rows) || null; @@ -524,6 +455,34 @@ export class ProjectService extends DBService { return (response && response.rows) || null; } + async getAttachmentsData(projectId: number): Promise { + const sqlStatement = queries.project.getAttachmentsByProjectSQL(projectId); + + if (!sqlStatement) { + throw new HTTP400('Failed to build SQL get statement'); + } + + const response = await this.connection.query(sqlStatement.text, sqlStatement.values); + + const result = (response && response.rows) || null; + + return new GetAttachmentsData(result); + } + + async getReportAttachmentsData(projectId: number): Promise { + const sqlStatement = queries.project.getReportAttachmentsByProjectSQL(projectId); + + if (!sqlStatement) { + throw new HTTP400('Failed to build SQL get statement'); + } + + const response = await this.connection.query(sqlStatement.text, sqlStatement.values); + + const result = (response && response.rows) || null; + + return new GetReportAttachmentsData(result); + } + async createProject(postProjectData: PostProjectObject): Promise { const projectId = await this.insertProject(postProjectData); @@ -556,24 +515,6 @@ export class ProjectService extends DBService { ) ); - // Handle new project permits - promises.push( - Promise.all( - postProjectData.permit.permits.map((permit: IPostPermit) => - this.insertPermit(permit.permit_number, permit.permit_type, projectId) - ) - ) - ); - - // Handle existing non-sampling permits which are now being associated to a project - promises.push( - Promise.all( - postProjectData.permit.existing_permits.map((existing_permit: IPostExistingPermit) => - this.associateExistingPermitToProject(existing_permit.permit_id, projectId) - ) - ) - ); - // Handle project IUCN classifications promises.push( Promise.all( @@ -677,46 +618,6 @@ export class ProjectService extends DBService { return result.id; } - async insertPermit(permitNumber: string, permitType: string, projectId: number): Promise { - const systemUserId = this.connection.systemUserId(); - - if (!systemUserId) { - throw new HTTP400('Failed to identify system user ID'); - } - - const sqlStatement = queries.permit.postProjectPermitSQL(permitNumber, permitType, projectId, systemUserId); - - if (!sqlStatement) { - throw new HTTP400('Failed to build SQL insert statement'); - } - - const response = await this.connection.query(sqlStatement.text, sqlStatement.values); - - const result = (response && response.rows && response.rows[0]) || null; - - if (!result || !result.id) { - throw new HTTP400('Failed to insert project permit data'); - } - - return result.id; - } - - async associateExistingPermitToProject(permitId: number, projectId: number): Promise { - const sqlStatement = queries.permit.associatePermitToProjectSQL(permitId, projectId); - - if (!sqlStatement) { - throw new HTTP400('Failed to build SQL update statement for associatePermitToProjectSQL'); - } - - const response = await this.connection.query(sqlStatement.text, sqlStatement.values); - - const result = (response && response.rowCount) || null; - - if (!result) { - throw new HTTP400('Failed to associate existing permit to project'); - } - } - async insertClassificationDetail(iucn3_id: number, project_id: number): Promise { const sqlStatement = queries.project.postProjectIUCNSQL(iucn3_id, project_id); @@ -788,10 +689,6 @@ export class ProjectService extends DBService { promises.push(this.updateProjectData(projectId, entities)); } - if (entities?.permit && entities?.coordinator) { - promises.push(this.updatePermitData(projectId, entities)); - } - if (entities?.iucn) { promises.push(this.updateIUCNData(projectId, entities)); } @@ -803,39 +700,6 @@ export class ProjectService extends DBService { await Promise.all(promises); } - async updatePermitData(projectId: number, entities: IUpdateProject): Promise { - if (!entities.permit) { - throw new HTTP400('Missing request body entity `permit`'); - } - - const putPermitData = new PostPermitData(entities.permit); - - const sqlDeleteStatement = queries.project.deletePermitSQL(projectId); - - if (!sqlDeleteStatement) { - throw new HTTP400('Failed to build SQL delete statement'); - } - - const deleteResult = await this.connection.query(sqlDeleteStatement.text, sqlDeleteStatement.values); - - if (!deleteResult) { - throw new HTTP409('Failed to delete project permit data'); - } - - const insertPermitPromises = - putPermitData?.permits?.map((permit: IPostPermit) => { - return this.insertPermit(permit.permit_number, permit.permit_type, projectId); - }) || []; - - // Handle existing non-sampling permits which are now being associated to a project - const updateExistingPermitPromises = - putPermitData?.existing_permits?.map((existing_permit: IPostExistingPermit) => { - return this.associateExistingPermitToProject(existing_permit.permit_id, projectId); - }) || []; - - await Promise.all([insertPermitPromises, updateExistingPermitPromises]); - } - async updateIUCNData(projectId: number, entities: IUpdateProject): Promise { const putIUCNData = (entities?.iucn && new PutIUCNData(entities.iucn)) || null; @@ -1014,28 +878,10 @@ export class ProjectService extends DBService { } } - async updatePublishStatus(projectId: number, publish: boolean): Promise { - const sqlStatement = queries.project.updateProjectPublishStatusSQL(projectId, publish); - - if (!sqlStatement) { - throw new HTTP400('Failed to build SQL statement'); - } - - const response = await this.connection.query(sqlStatement.text, sqlStatement.values); - const result = (response && response.rows && response.rows[0]) || null; - - if (!response || !result) { - throw new HTTP500('Failed to update project publish status'); - } - - return result.id; - } - - async deleteProject(projectId: number, userRoles: string | string[]): Promise { + async deleteProject(projectId: number): Promise { /** * PART 1 - * Check that user is a system administrator - can delete a project (published or not) - * Check that user is a project administrator - can delete a project (unpublished only) + * Check that user is a system administrator - can delete a project * */ const getProjectSQLStatement = queries.project.getProjectSQL(projectId); @@ -1052,10 +898,6 @@ export class ProjectService extends DBService { throw new HTTP400('Failed to get the project'); } - if (projectResult.publish_date && userHasValidRole([SYSTEM_ROLE.PROJECT_CREATOR], userRoles)) { - throw new HTTP400('Cannot delete a published project if you are not a system administrator.'); - } - /** * PART 2 * Get the attachment S3 keys for all attachments associated to this project and surveys under this project diff --git a/api/src/services/survey-service.test.ts b/api/src/services/survey-service.test.ts index 92fb91e889..130964be1d 100644 --- a/api/src/services/survey-service.test.ts +++ b/api/src/services/survey-service.test.ts @@ -1,10 +1,28 @@ import chai, { expect } from 'chai'; import { describe } from 'mocha'; +import { QueryResult } from 'pg'; import sinon from 'sinon'; import sinonChai from 'sinon-chai'; -import { PutSurveyObject } from '../models/survey-update'; +import { ApiGeneralError } from '../errors/custom-error'; +import { GetReportAttachmentsData } from '../models/project-view'; +import { PostProprietorData, PostSurveyObject } from '../models/survey-create'; +import { PutSurveyObject, PutSurveyPermitData } from '../models/survey-update'; +import { + GetAncillarySpeciesData, + GetAttachmentsData, + GetFocalSpeciesData, + GetPermitData, + GetSurveyData, + GetSurveyFundingSources, + GetSurveyLocationData, + GetSurveyProprietorData, + GetSurveyPurposeAndMethodologyData +} from '../models/survey-view'; +import { IPermitModel } from '../repositories/permit-repository'; import { getMockDBConnection } from '../__mocks__/db'; +import { PermitService } from './permit-service'; import { SurveyService } from './survey-service'; +import { TaxonomyService } from './taxonomy-service'; chai.use(sinonChai); @@ -30,11 +48,10 @@ describe('SurveyService', () => { const surveyService = new SurveyService(dbConnectionObj); - const projectId = 1; const surveyId = 2; const putSurveyData = new PutSurveyObject(null); - await surveyService.updateSurvey(projectId, surveyId, putSurveyData); + await surveyService.updateSurvey(surveyId, putSurveyData); expect(updateSurveyDetailsDataStub).not.to.have.been.called; expect(updateSurveyVantageCodesDataStub).not.to.have.been.called; @@ -60,7 +77,6 @@ describe('SurveyService', () => { const surveyService = new SurveyService(dbConnectionObj); - const projectId = 1; const surveyId = 2; const putSurveyData = new PutSurveyObject({ survey_details: {}, @@ -72,7 +88,7 @@ describe('SurveyService', () => { location: {} }); - await surveyService.updateSurvey(projectId, surveyId, putSurveyData); + await surveyService.updateSurvey(surveyId, putSurveyData); expect(updateSurveyDetailsDataStub).to.have.been.calledOnce; expect(updateSurveyVantageCodesDataStub).to.have.been.calledOnce; @@ -82,4 +98,1078 @@ describe('SurveyService', () => { expect(updateSurveyProprietorDataStub).to.have.been.calledOnce; }); }); + + describe('getLatestSurveyOccurrenceSubmission', () => { + afterEach(() => { + sinon.restore(); + }); + + it('Gets latest survey submission', async () => { + const mockRowObj = { id: 123 }; + const mockQueryResponse = ({ rows: [mockRowObj] } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + + const surveyId = 1; + + const surveyService = new SurveyService(mockDBConnection); + + const response = await surveyService.getLatestSurveyOccurrenceSubmission(surveyId); + + expect(response).to.eql({ id: 123 }); + }); + }); + + describe('getSurveyIdsByProjectId', () => { + afterEach(() => { + sinon.restore(); + }); + + it('Gets survey ids by project id', async () => { + const mockRowObj = { id: 123 }; + const mockQueryResponse = ({ rows: [mockRowObj] } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ sql: async () => mockQueryResponse }); + + const projectId = 1; + + const surveyService = new SurveyService(mockDBConnection); + + const response = await surveyService.getSurveyIdsByProjectId(projectId); + + expect(response).to.eql([{ id: 123 }]); + }); + }); + + describe('getSurveyById', () => { + afterEach(() => { + sinon.restore(); + }); + + it('Gets survey data by id', async () => { + const getSurveyDataStub = sinon + .stub(SurveyService.prototype, 'getSurveyData') + .resolves(({ id: 1 } as unknown) as GetSurveyData); + + const getSpeciesDataStub = sinon + .stub(SurveyService.prototype, 'getSpeciesData') + .resolves(({ focal_species: [1] } as unknown) as GetFocalSpeciesData & GetAncillarySpeciesData); + + const getPermitDataStub = sinon + .stub(SurveyService.prototype, 'getPermitData') + .resolves(({ permit_number: 1 } as unknown) as GetPermitData); + + const getSurveyFundingSourcesDataStub = sinon + .stub(SurveyService.prototype, 'getSurveyFundingSourcesData') + .resolves(({ funding_sources: [1] } as unknown) as GetSurveyFundingSources); + + const getSurveyPurposeAndMethodologyStub = sinon + .stub(SurveyService.prototype, 'getSurveyPurposeAndMethodology') + .resolves(({ GetSurveyPurposeAndMethodologyData: 1 } as unknown) as GetSurveyPurposeAndMethodologyData); + + const getSurveyProprietorDataForViewStub = sinon + .stub(SurveyService.prototype, 'getSurveyProprietorDataForView') + .resolves(({ proprietor_type_id: 1 } as unknown) as GetSurveyProprietorData); + + const getSurveyLocationDataStub = sinon + .stub(SurveyService.prototype, 'getSurveyLocationData') + .resolves(({ survey_area_name: 'name' } as unknown) as GetSurveyLocationData); + + const surveyService = new SurveyService(getMockDBConnection()); + const response = await surveyService.getSurveyById(1); + + expect(response).to.eql({ + survey_details: { id: 1 }, + species: { focal_species: [1] }, + permit: { permit_number: 1 }, + purpose_and_methodology: { GetSurveyPurposeAndMethodologyData: 1 }, + funding: { funding_sources: [1] }, + proprietor: { proprietor_type_id: 1 }, + location: { survey_area_name: 'name' } + }); + + expect(getSurveyDataStub).to.be.calledOnce; + expect(getSpeciesDataStub).to.be.calledOnce; + expect(getPermitDataStub).to.be.calledOnce; + expect(getSurveyFundingSourcesDataStub).to.be.calledOnce; + expect(getSurveyPurposeAndMethodologyStub).to.be.calledOnce; + expect(getSurveyProprietorDataForViewStub).to.be.calledOnce; + expect(getSurveyLocationDataStub).to.be.calledOnce; + }); + }); + + describe('getSurveySupplementaryDataById', () => { + afterEach(() => { + sinon.restore(); + }); + + it('Gets data if no errors', async () => { + const getOccurrenceSubmissionIdStub = sinon + .stub(SurveyService.prototype, 'getOccurrenceSubmissionId') + .resolves(({ occurrence_submission: 1 } as unknown) as any); + + const getSummaryResultIdStub = sinon + .stub(SurveyService.prototype, 'getSummaryResultId') + .resolves(({ survey_summary_submission: 1 } as unknown) as any); + + const surveyService = new SurveyService(getMockDBConnection()); + + const response = await surveyService.getSurveySupplementaryDataById(1); + + expect(response).to.eql({ + occurrence_submission: { occurrence_submission: 1 }, + summary_result: { survey_summary_submission: 1 } + }); + expect(getOccurrenceSubmissionIdStub).to.be.calledOnce; + expect(getSummaryResultIdStub).to.be.calledOnce; + }); + }); + + describe('getSurveyData', () => { + afterEach(() => { + sinon.restore(); + }); + + it('throws api error if response is null', async () => { + const mockQueryResponse = ({} as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ sql: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + try { + await surveyService.getSurveyData(1); + expect.fail(); + } catch (actualError) { + expect((actualError as ApiGeneralError).message).to.equal('Failed to get project survey details data'); + } + }); + + it('Gets all survey data if response is not null', async () => { + const mockRowObj = { id: 123 }; + const mockQueryResponse = ({ rows: [mockRowObj] } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ sql: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + const response = await surveyService.getSurveyData(1); + + expect(response).to.eql(new GetSurveyData(mockRowObj)); + }); + }); + + describe('getSpeciesData', () => { + afterEach(() => { + sinon.restore(); + }); + + it('throws api error if response is null', async () => { + const mockQueryResponse = ({} as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + try { + await surveyService.getSpeciesData(1); + expect.fail(); + } catch (actualError) { + expect((actualError as ApiGeneralError).message).to.equal('Failed to get survey species data'); + } + }); + + it('returns data if response is not null', async () => { + const getSpeciesFromIds = sinon + .stub(TaxonomyService.prototype, 'getSpeciesFromIds') + .resolves(([{ id: 123 }] as unknown) as any); + + const mockRowObj = [ + { is_focal: true, wldtaxonomic_units_id: 123 }, + { is_focal: false, wldtaxonomic_units_id: 321 } + ]; + const mockQueryResponse = ({ rows: [mockRowObj] } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + const response = await surveyService.getSpeciesData(1); + + expect(response).to.eql({ + ...new GetFocalSpeciesData([{ id: 123 }]), + ...new GetAncillarySpeciesData([{ id: 123 }]) + }); + expect(getSpeciesFromIds).to.be.calledTwice; + }); + }); + + describe('getPermitData', () => { + afterEach(() => { + sinon.restore(); + }); + + it('returns data if valid return', async () => { + const mockPermitResponse: IPermitModel[] = [ + { + permit_id: 1, + survey_id: 1, + number: 'abc', + type: 'Fisheries', + create_date: '2022-02-02', + create_user: 4, + update_date: '2022-02-02', + update_user: 4, + revision_count: 1 + } + ]; + + const mockDBConnection = getMockDBConnection(); + const surveyService = new SurveyService(mockDBConnection); + + const getPermitBySurveyIdStub = sinon + .stub(PermitService.prototype, 'getPermitBySurveyId') + .resolves(mockPermitResponse); + + const response = await surveyService.getPermitData(1); + + expect(getPermitBySurveyIdStub).to.be.calledOnceWith(1); + expect(response).to.eql({ permits: [{ permit_id: 1, permit_number: 'abc', permit_type: 'Fisheries' }] }); + }); + }); + + describe('getSurveyPurposeAndMethodology', () => { + afterEach(() => { + sinon.restore(); + }); + + it('returns data if valid return', async () => { + const mockQueryResponse = ({ rows: [{ id: 1 }], rowCount: 0 } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + const response = await surveyService.getSurveyPurposeAndMethodology(1); + + expect(response).to.eql(new GetSurveyPurposeAndMethodologyData({ id: 1 })); + }); + + it('throws error if response is invalid', async () => { + const mockQueryResponse = ({ rows: [] } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + try { + await surveyService.getSurveyPurposeAndMethodology(1); + expect.fail(); + } catch (actualError) { + expect((actualError as ApiGeneralError).message).to.equal('Failed to get survey purpose and methodology data'); + } + }); + }); + + describe('getSurveyFundingSourcesData', () => { + afterEach(() => { + sinon.restore(); + }); + + it('returns data if valid return', async () => { + const mockQueryResponse = ({ rows: [{ id: 1 }], rowCount: 0 } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + const response = await surveyService.getSurveyFundingSourcesData(1); + + expect(response).to.eql(new GetSurveyFundingSources([{ id: 1 }])); + }); + + it('throws error if response is invalid', async () => { + const mockQueryResponse = ({} as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + try { + await surveyService.getSurveyFundingSourcesData(1); + expect.fail(); + } catch (actualError) { + expect((actualError as ApiGeneralError).message).to.equal('Failed to get survey funding sources data'); + } + }); + }); + + describe('getSurveyProprietorDataForView', () => { + afterEach(() => { + sinon.restore(); + }); + + it('returns data if valid return', async () => { + const mockQueryResponse = ({ rows: [{ id: 1 }], rowCount: 0 } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + const response = await surveyService.getSurveyProprietorDataForView(1); + + expect(response).to.eql(new GetSurveyProprietorData([{ id: 1 }])); + }); + }); + + describe('getSurveyLocationData', () => { + afterEach(() => { + sinon.restore(); + }); + + it('returns data if valid return', async () => { + const mockQueryResponse = ({ rows: [{ id: 1 }], rowCount: 0 } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ sql: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + const response = await surveyService.getSurveyLocationData(1); + + expect(response).to.eql(new GetSurveyLocationData({ id: 1 })); + }); + + it('throws error if response is invalid', async () => { + const mockQueryResponse = ({ rows: [] } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ sql: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + try { + await surveyService.getSurveyLocationData(1); + expect.fail(); + } catch (actualError) { + expect((actualError as ApiGeneralError).message).to.equal('Failed to get project survey details data'); + } + }); + }); + + describe('getOccurrenceSubmissionId', () => { + afterEach(() => { + sinon.restore(); + }); + + it('returns data if valid return', async () => { + const mockQueryResponse = ({ rows: [{ id: 1 }], rowCount: 0 } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + const response = await surveyService.getOccurrenceSubmissionId(1); + + expect(response).to.eql({ id: 1 }); + }); + + it('returns null if response is empty', async () => { + const mockQueryResponse = ({ rows: [], rowCount: 0 } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + const response = await surveyService.getOccurrenceSubmissionId(1); + + expect(response).to.eql(null); + }); + }); + + describe('getLatestSurveyOccurrenceSubmission', () => { + afterEach(() => { + sinon.restore(); + }); + + it('returns data if valid return', async () => { + const mockQueryResponse = ({ rows: [{ id: 1 }], rowCount: 0 } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + const response = await surveyService.getLatestSurveyOccurrenceSubmission(1); + + expect(response).to.eql({ id: 1 }); + }); + + it('returns null if response is empty', async () => { + const mockQueryResponse = ({ rows: [], rowCount: 0 } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + const response = await surveyService.getLatestSurveyOccurrenceSubmission(1); + + expect(response).to.eql(null); + }); + }); + + describe('getSummaryResultId', () => { + afterEach(() => { + sinon.restore(); + }); + + it('returns data if valid return', async () => { + const mockQueryResponse = ({ rows: [{ id: 1 }], rowCount: 0 } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + const response = await surveyService.getSummaryResultId(1); + + expect(response).to.eql({ id: 1 }); + }); + + it('returns null if response is empty', async () => { + const mockQueryResponse = ({ rows: [], rowCount: 0 } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + const response = await surveyService.getSummaryResultId(1); + + expect(response).to.eql(null); + }); + }); + + describe('getAttachmentsData', () => { + afterEach(() => { + sinon.restore(); + }); + + it('returns data if valid return', async () => { + const mockQueryResponse = ({ rows: [{ id: 1 }], rowCount: 0 } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + const response = await surveyService.getAttachmentsData(1); + + expect(response).to.eql(new GetAttachmentsData([{ id: 1 }])); + }); + }); + + describe('getReportAttachmentsData', () => { + afterEach(() => { + sinon.restore(); + }); + + it('returns data if valid return', async () => { + const mockQueryResponse = ({ rows: [{ id: 1 }], rowCount: 0 } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + const response = await surveyService.getReportAttachmentsData(1); + + expect(response).to.eql(new GetReportAttachmentsData([{ id: 1 }])); + }); + }); + + describe('insertSurveyData', () => { + afterEach(() => { + sinon.restore(); + }); + + it('returns data if valid return', async () => { + const mockQueryResponse = ({ rows: [{ id: 1 }], rowCount: 0 } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ sql: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + const response = await surveyService.insertSurveyData(1, ({ + survey_details: { + survey_name: 'name', + start_date: 'date', + end_date: 'date', + biologist_first_name: 'name', + biologist_last_name: 'name' + }, + purpose_and_methodology: { + field_method_id: 'name', + additional_details: 'date', + ecological_season_id: 'date', + intended_outcome_id: 'name', + surveyed_all_areas: 'name' + }, + location: { survey_area_name: 'name', geometry: [{ stuff: 'geometry' }] } + } as unknown) as PostSurveyObject); + + expect(response).to.eql(1); + }); + + it('throws error if response is invalid', async () => { + const mockQueryResponse = ({ rows: [] } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ sql: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + try { + await surveyService.insertSurveyData(1, ({ + survey_details: { + survey_name: 'name', + start_date: 'date', + end_date: 'date', + biologist_first_name: 'name', + biologist_last_name: 'name' + }, + purpose_and_methodology: { + field_method_id: 'name', + additional_details: 'date', + ecological_season_id: 'date', + intended_outcome_id: 'name', + surveyed_all_areas: 'name' + }, + location: { survey_area_name: 'name', geometry: [{ stuff: 'geometry' }] } + } as unknown) as PostSurveyObject); + expect.fail(); + } catch (actualError) { + expect((actualError as ApiGeneralError).message).to.equal('Failed to insert survey data'); + } + }); + }); + + describe('insertFocalSpecies', () => { + afterEach(() => { + sinon.restore(); + }); + + it('returns data if valid return', async () => { + const mockQueryResponse = ({ rows: [{ id: 1 }], rowCount: 0 } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + const response = await surveyService.insertFocalSpecies(1, 1); + + expect(response).to.eql(1); + }); + + it('throws error if response is invalid', async () => { + const mockQueryResponse = ({ rows: [] } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + try { + await surveyService.insertFocalSpecies(1, 1); + expect.fail(); + } catch (actualError) { + expect((actualError as ApiGeneralError).message).to.equal('Failed to insert focal species data'); + } + }); + }); + + describe('insertAncillarySpecies', () => { + afterEach(() => { + sinon.restore(); + }); + + it('returns data if valid return', async () => { + const mockQueryResponse = ({ rows: [{ id: 1 }], rowCount: 0 } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + const response = await surveyService.insertAncillarySpecies(1, 1); + + expect(response).to.eql(1); + }); + + it('throws error if response is invalid', async () => { + const mockQueryResponse = ({ rows: [] } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + try { + await surveyService.insertAncillarySpecies(1, 1); + expect.fail(); + } catch (actualError) { + expect((actualError as ApiGeneralError).message).to.equal('Failed to insert ancillary species data'); + } + }); + }); + + describe('insertVantageCodes', () => { + afterEach(() => { + sinon.restore(); + }); + + it('returns data if valid return', async () => { + const mockQueryResponse = ({ rows: [{ id: 1 }], rowCount: 0 } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + const response = await surveyService.insertVantageCodes(1, 1); + + expect(response).to.eql(1); + }); + + it('throws error if response is invalid', async () => { + const mockQueryResponse = ({ rows: [] } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + try { + await surveyService.insertVantageCodes(1, 1); + expect.fail(); + } catch (actualError) { + expect((actualError as ApiGeneralError).message).to.equal('Failed to insert ancillary species data'); + } + }); + }); + + describe('insertSurveyProprietor', () => { + afterEach(() => { + sinon.restore(); + }); + + it('returns in survey_data_proprietary is undefinded', async () => { + const mockQueryResponse = ({ rows: [], rowCount: 0 } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + const response = await surveyService.insertSurveyProprietor(({ prt_id: 1 } as unknown) as PostProprietorData, 1); + + expect(response).to.eql(undefined); + }); + + it('throws error if response is invalid', async () => { + const mockQueryResponse = ({ rows: [] } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + try { + await surveyService.insertSurveyProprietor( + ({ survey_data_proprietary: 'data', prt_id: 1 } as unknown) as PostProprietorData, + 1 + ); + expect.fail(); + } catch (actualError) { + expect((actualError as ApiGeneralError).message).to.equal('Failed to insert survey proprietor data'); + } + }); + + it('returns data if response is not null', async () => { + const mockQueryResponse = ({ rows: [{ id: 1 }], rowCount: 1 } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + const response = await surveyService.insertSurveyProprietor( + ({ survey_data_proprietary: 'data', prt_id: 1 } as unknown) as PostProprietorData, + 1 + ); + + expect(response).to.eql(1); + }); + }); + + describe('insertOrAssociatePermitToSurvey', () => { + afterEach(() => { + sinon.restore(); + }); + + it('throws api error if response is null', async () => { + const mockQueryResponse = ({ rows: [], rowCount: 0 } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ sql: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + try { + await surveyService.insertOrAssociatePermitToSurvey(1, 1, 1, 'string', 'type'); + expect.fail(); + } catch (actualError) { + expect((actualError as ApiGeneralError).message).to.equal('Failed to upsert survey permit record'); + } + }); + + it('returns data if response is not null', async () => { + const mockQueryResponse = ({ rows: [{ data: 1 }], rowCount: 1 } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ sql: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + const response = await surveyService.insertOrAssociatePermitToSurvey(1, 1, 1, 'string', ''); + + expect(response).to.eql(undefined); + }); + }); + + describe('insertSurveyFundingSource', () => { + afterEach(() => { + sinon.restore(); + }); + + it('throws api error if response is null', async () => { + const mockDBConnection = getMockDBConnection({ query: async () => (undefined as unknown) as any }); + const surveyService = new SurveyService(mockDBConnection); + + try { + await surveyService.insertSurveyFundingSource(1, 1); + expect.fail(); + } catch (actualError) { + expect((actualError as ApiGeneralError).message).to.equal('Failed to insert survey funding source data'); + } + }); + + it('returns data if response is not null', async () => { + const mockQueryResponse = ({ response: 'something' } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + const response = await surveyService.insertSurveyFundingSource(1, 1); + + expect(response).to.eql(undefined); + }); + }); + + describe('updateSurveyDetailsData', () => { + afterEach(() => { + sinon.restore(); + }); + + it('throws api error if response is null', async () => { + const mockDBConnection = getMockDBConnection({ knex: async () => (undefined as unknown) as any }); + const surveyService = new SurveyService(mockDBConnection); + + try { + await surveyService.updateSurveyDetailsData(1, ({ survey_details: 'details' } as unknown) as PutSurveyObject); + expect.fail(); + } catch (actualError) { + expect((actualError as ApiGeneralError).message).to.equal('Failed to update survey data'); + } + }); + + it('returns data if response is not null', async () => { + const mockQueryResponse = ({ response: 'something', rowCount: 1 } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ knex: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + const response = await surveyService.updateSurveyDetailsData(1, ({ + survey_details: 'details' + } as unknown) as PutSurveyObject); + + expect(response).to.eql(undefined); + }); + }); + + describe('updateSurveySpeciesData', () => { + afterEach(() => { + sinon.restore(); + }); + + it('returns data if response is not null', async () => { + sinon.stub(SurveyService.prototype, 'deleteSurveySpeciesData').resolves(); + sinon.stub(SurveyService.prototype, 'insertFocalSpecies').resolves(1); + sinon.stub(SurveyService.prototype, 'insertAncillarySpecies').resolves(1); + + const mockQueryResponse = ({ response: 'something', rowCount: 1 } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ knex: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + const response = await surveyService.updateSurveySpeciesData(1, ({ + survey_details: 'details', + species: { focal_species: [1], ancillary_species: [1] } + } as unknown) as PutSurveyObject); + + expect(response).to.eql([1, 1]); + }); + }); + + describe('deleteSurveySpeciesData', () => { + afterEach(() => { + sinon.restore(); + }); + + it('returns data if response is not null', async () => { + const mockQueryResponse = (undefined as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ sql: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + const response = await surveyService.deleteSurveySpeciesData(1); + + expect(response).to.eql(undefined); + }); + }); + + describe('updateSurveyPermitData', () => { + afterEach(() => { + sinon.restore(); + }); + + describe('with no existing permits', () => { + it('handles permit deletes/updates/creates', async () => { + const mockDBConnection = getMockDBConnection(); + + const getPermitBySurveyIdStub = sinon.stub(PermitService.prototype, 'getPermitBySurveyId').resolves([]); + const deleteSurveyPermitStub = sinon.stub(PermitService.prototype, 'deleteSurveyPermit').resolves(); + const updateSurveyPermitStub = sinon.stub(PermitService.prototype, 'updateSurveyPermit').resolves(); + const createSurveyPermitStub = sinon.stub(PermitService.prototype, 'createSurveyPermit').resolves(); + + const mockPutSurveyObject = { + permit: { + permits: [ + { + permit_id: 2, + permit_number: '1111', + permit_type: 'type1' + }, + { + permit_number: '2222', + permit_type: 'type2' + } + ] + } as PutSurveyPermitData + } as PutSurveyObject; + + const surveyService = new SurveyService(mockDBConnection); + + await surveyService.updateSurveyPermitData(1, mockPutSurveyObject); + + expect(getPermitBySurveyIdStub).to.have.been.calledOnceWith(1); + + expect(deleteSurveyPermitStub).not.to.have.been.called; + + expect(updateSurveyPermitStub).to.have.been.calledOnceWith(1, 2, '1111', 'type1'); + + expect(createSurveyPermitStub).to.have.been.calledOnceWith(1, '2222', 'type2'); + }); + }); + + describe('with existing permits', () => { + it('handles permit deletes/updates/creates', async () => { + const mockDBConnection = getMockDBConnection(); + + const mockExistingPermits = [{ permit_id: 3 }, { permit_id: 4 }] as IPermitModel[]; + + const getPermitBySurveyIdStub = sinon + .stub(PermitService.prototype, 'getPermitBySurveyId') + .resolves(mockExistingPermits); + const deleteSurveyPermitStub = sinon.stub(PermitService.prototype, 'deleteSurveyPermit').resolves(); + const updateSurveyPermitStub = sinon.stub(PermitService.prototype, 'updateSurveyPermit').resolves(); + const createSurveyPermitStub = sinon.stub(PermitService.prototype, 'createSurveyPermit').resolves(); + + const mockPutSurveyObject = { + permit: { + permits: [ + { + permit_id: 2, + permit_number: '1111', + permit_type: 'type1' + }, + { + permit_number: '2222', + permit_type: 'type2' + } + ] + } as PutSurveyPermitData + } as PutSurveyObject; + + const surveyService = new SurveyService(mockDBConnection); + + await surveyService.updateSurveyPermitData(1, mockPutSurveyObject); + + expect(getPermitBySurveyIdStub).to.have.been.calledOnceWith(1); + + expect(deleteSurveyPermitStub).to.have.callCount(2); + expect(deleteSurveyPermitStub).to.have.been.calledWith(1, 3); + expect(deleteSurveyPermitStub).to.have.been.calledWith(1, 4); + + expect(updateSurveyPermitStub).to.have.been.calledOnceWith(1, 2, '1111', 'type1'); + + expect(createSurveyPermitStub).to.have.been.calledOnceWith(1, '2222', 'type2'); + }); + }); + }); + + describe('unassociatePermitFromSurvey', () => { + afterEach(() => { + sinon.restore(); + }); + + it('returns data if response is not null', async () => { + const mockQueryResponse = (undefined as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ sql: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + const response = await surveyService.unassociatePermitFromSurvey(1); + + expect(response).to.eql(undefined); + }); + }); + + describe('updateSurveyFundingData', () => { + afterEach(() => { + sinon.restore(); + }); + + it('returns data if response is not null', async () => { + sinon.stub(SurveyService.prototype, 'deleteSurveyFundingSourcesData').resolves(undefined); + sinon.stub(SurveyService.prototype, 'insertSurveyFundingSource').resolves(undefined); + + const mockQueryResponse = (undefined as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ sql: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + const response = await surveyService.updateSurveyFundingData(1, ({ + permit: { permit_number: '1', permit_type: 'type' }, + funding: { funding_sources: [1] } + } as unknown) as PutSurveyObject); + + expect(response).to.eql([undefined]); + }); + }); + + describe('deleteSurveyFundingSourcesData', () => { + afterEach(() => { + sinon.restore(); + }); + + it('returns data if response is not null', async () => { + const mockQueryResponse = (undefined as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ sql: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + const response = await surveyService.deleteSurveyFundingSourcesData(1); + + expect(response).to.eql(undefined); + }); + }); + + describe('updateSurveyProprietorData', () => { + afterEach(() => { + sinon.restore(); + }); + + it('returns undefined if not survey_data_proprietary is given', async () => { + const mockQueryResponse = (undefined as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ sql: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + const response = await surveyService.updateSurveyProprietorData(1, ({ + permit: { permit_number: '1', permit_type: 'type' }, + funding: { funding_sources: [1] }, + proprietor: { survey_data_proprietary: undefined } + } as unknown) as PutSurveyObject); + + expect(response).to.eql(undefined); + }); + + it('returns data if response is not null', async () => { + sinon.stub(SurveyService.prototype, 'insertSurveyProprietor').resolves(1); + + const mockQueryResponse = (undefined as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ sql: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + const response = await surveyService.updateSurveyProprietorData(1, ({ + permit: { permit_number: '1', permit_type: 'type' }, + funding: { funding_sources: [1] }, + proprietor: { survey_data_proprietary: 'asd' } + } as unknown) as PutSurveyObject); + + expect(response).to.eql(1); + }); + }); + + describe('deleteSurveyProprietorData', () => { + afterEach(() => { + sinon.restore(); + }); + + it('returns data if response is not null', async () => { + const mockQueryResponse = (undefined as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ sql: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + const response = await surveyService.deleteSurveyProprietorData(1); + + expect(response).to.eql(undefined); + }); + }); + + describe('updateSurveyVantageCodesData', () => { + afterEach(() => { + sinon.restore(); + }); + + it('returns [] if not vantage_code_ids is given', async () => { + sinon.stub(SurveyService.prototype, 'deleteSurveyVantageCodes').resolves(); + + const mockQueryResponse = (undefined as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ sql: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + const response = await surveyService.updateSurveyVantageCodesData(1, ({ + permit: { permit_number: '1', permit_type: 'type' }, + funding: { funding_sources: [1] }, + purpose_and_methodology: { vantage_code_ids: undefined } + } as unknown) as PutSurveyObject); + + expect(response).to.eql([]); + }); + + it('returns data if response is not null', async () => { + sinon.stub(SurveyService.prototype, 'deleteSurveyVantageCodes').resolves(); + sinon.stub(SurveyService.prototype, 'insertVantageCodes').resolves(1); + + const mockQueryResponse = (undefined as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ sql: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + const response = await surveyService.updateSurveyVantageCodesData(1, ({ + permit: { permit_number: '1', permit_type: 'type' }, + funding: { funding_sources: [1] }, + proprietor: { survey_data_proprietary: 'asd' }, + purpose_and_methodology: { vantage_code_ids: [1] } + } as unknown) as PutSurveyObject); + + expect(response).to.eql([1]); + }); + }); + + describe('deleteSurveyVantageCodes', () => { + afterEach(() => { + sinon.restore(); + }); + + it('throws errors if response is empty', async () => { + const mockQueryResponse = (undefined as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + try { + await surveyService.deleteSurveyVantageCodes(1); + expect.fail(); + } catch (actualError) { + expect((actualError as ApiGeneralError).message).to.equal('Failed to delete survey vantage codes'); + } + }); + + it('returns data if response is not null', async () => { + const mockQueryResponse = ({ rows: [] } as unknown) as QueryResult; + + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + const surveyService = new SurveyService(mockDBConnection); + + const response = await surveyService.deleteSurveyVantageCodes(1); + + expect(response).to.eql(undefined); + }); + }); }); diff --git a/api/src/services/survey-service.ts b/api/src/services/survey-service.ts index aaf0d930cb..578008c126 100644 --- a/api/src/services/survey-service.ts +++ b/api/src/services/survey-service.ts @@ -4,8 +4,10 @@ import { PostProprietorData, PostSurveyObject } from '../models/survey-create'; import { PutSurveyObject } from '../models/survey-update'; import { GetAncillarySpeciesData, + GetAttachmentsData, GetFocalSpeciesData, GetPermitData, + GetReportAttachmentsData, GetSurveyData, GetSurveyFundingSources, GetSurveyLocationData, @@ -15,6 +17,7 @@ import { SurveySupplementaryData } from '../models/survey-view'; import { queries } from '../queries/queries'; +import { PermitService } from './permit-service'; import { DBService } from './service'; import { TaxonomyService } from './taxonomy-service'; @@ -126,22 +129,9 @@ export class SurveyService extends DBService { } async getPermitData(surveyId: number): Promise { - const sqlStatement = SQL` - SELECT - number, - type - FROM - permit - WHERE - survey_id = ${surveyId}; - `; + const permitService = new PermitService(this.connection); - const response = await this.connection.query<{ number: string; type: string }>( - sqlStatement.text, - sqlStatement.values - ); - - const result = response.rows?.[0]; + const result = await permitService.getPermitBySurveyId(surveyId); return new GetPermitData(result); } @@ -162,7 +152,6 @@ export class SurveyService extends DBService { async getSurveyFundingSourcesData(surveyId: number): Promise { const sqlStatement = queries.survey.getSurveyFundingSourcesDataForViewSQL(surveyId); - if (!sqlStatement) { throw new ApiGeneralError('Failed to build SQL get statement'); } @@ -213,7 +202,24 @@ export class SurveyService extends DBService { async getOccurrenceSubmissionId(surveyId: number) { const sqlStatement = queries.survey.getLatestOccurrenceSubmissionIdSQL(surveyId); + if (!sqlStatement) { + throw new ApiGeneralError('Failed to build SQL get statement'); + } + + const response = await this.connection.query(sqlStatement.text, sqlStatement.values); + return (response && response.rows?.[0]) || null; + } + + /** + * Get latest survey data submission from id + * + * @param {number} surveyId + * @return {*} + * @memberof SurveyService + */ + async getLatestSurveyOccurrenceSubmission(surveyId: number) { + const sqlStatement = queries.survey.getLatestSurveyOccurrenceSubmissionSQL(surveyId); if (!sqlStatement) { throw new ApiGeneralError('Failed to build SQL get statement'); } @@ -268,17 +274,14 @@ export class SurveyService extends DBService { ); // Handle inserting any permit associated to this survey - if (postSurveyData.permit.permit_number) { - promises.push( - this.insertOrAssociatePermitToSurvey( - this.connection.systemUserId() as number, - projectId, - surveyId, - postSurveyData.permit.permit_number, - postSurveyData.permit.permit_type + const permitService = new PermitService(this.connection); + promises.push( + Promise.all( + postSurveyData.permit.permits.map((permit) => + permitService.createSurveyPermit(surveyId, permit.permit_number, permit.permit_type) ) - ); - } + ) + ); // Handle inserting any funding sources associated to this survey promises.push( @@ -304,16 +307,29 @@ export class SurveyService extends DBService { return surveyId; } + async getAttachmentsData(surveyId: number): Promise { + const sqlStatement = queries.survey.getAttachmentsBySurveySQL(surveyId); + + const response = await this.connection.query(sqlStatement.text, sqlStatement.values); + const result = (response && response.rows) || null; + + return new GetAttachmentsData(result); + } + + async getReportAttachmentsData(surveyId: number): Promise { + const sqlStatement = queries.survey.getReportAttachmentsBySurveySQL(surveyId); + + const response = await this.connection.query(sqlStatement.text, sqlStatement.values); + const result = (response && response.rows) || null; + + return new GetReportAttachmentsData(result); + } + async insertSurveyData(projectId: number, surveyData: PostSurveyObject): Promise { const sqlStatement = queries.survey.postSurveySQL(projectId, surveyData); - if (!sqlStatement) { - throw new ApiGeneralError('Failed to build survey SQL insert statement'); - } - const response = await this.connection.sql(sqlStatement); - - const result = response.rows[0] || null; + const result = (response && response.rows && response.rows[0]) || null; if (!result) { throw new ApiGeneralError('Failed to insert survey data'); @@ -325,10 +341,6 @@ export class SurveyService extends DBService { async insertFocalSpecies(focal_species_id: number, surveyId: number): Promise { const sqlStatement = queries.survey.postFocalSpeciesSQL(focal_species_id, surveyId); - if (!sqlStatement) { - throw new ApiGeneralError('Failed to build SQL insert statement'); - } - const response = await this.connection.query(sqlStatement.text, sqlStatement.values); const result = (response && response.rows && response.rows[0]) || null; @@ -342,10 +354,6 @@ export class SurveyService extends DBService { async insertAncillarySpecies(ancillary_species_id: number, surveyId: number): Promise { const sqlStatement = queries.survey.postAncillarySpeciesSQL(ancillary_species_id, surveyId); - if (!sqlStatement) { - throw new ApiGeneralError('Failed to build SQL insert statement'); - } - const response = await this.connection.query(sqlStatement.text, sqlStatement.values); const result = (response && response.rows && response.rows[0]) || null; @@ -359,10 +367,6 @@ export class SurveyService extends DBService { async insertVantageCodes(vantage_code_id: number, surveyId: number): Promise { const sqlStatement = queries.survey.postVantageCodesSQL(vantage_code_id, surveyId); - if (!sqlStatement) { - throw new ApiGeneralError('Failed to build SQL insert statement'); - } - const response = await this.connection.query(sqlStatement.text, sqlStatement.values); const result = (response && response.rows && response.rows[0]) || null; @@ -380,10 +384,6 @@ export class SurveyService extends DBService { const sqlStatement = queries.survey.postSurveyProprietorSQL(surveyId, survey_proprietor); - if (!sqlStatement) { - throw new ApiGeneralError('Failed to build SQL insert statement'); - } - const response = await this.connection.query(sqlStatement.text, sqlStatement.values); const result = (response && response.rows && response.rows[0]) || null; @@ -419,10 +419,6 @@ export class SurveyService extends DBService { async insertSurveyFundingSource(funding_source_id: number, surveyId: number) { const sqlStatement = queries.survey.insertSurveyFundingSourceSQL(surveyId, funding_source_id); - if (!sqlStatement) { - throw new ApiGeneralError('Failed to build SQL statement for insertSurveyFundingSource'); - } - const response = await this.connection.query(sqlStatement.text, sqlStatement.values); if (!response) { @@ -430,7 +426,7 @@ export class SurveyService extends DBService { } } - async updateSurvey(projectId: number, surveyId: number, putSurveyData: PutSurveyObject): Promise { + async updateSurvey(surveyId: number, putSurveyData: PutSurveyObject): Promise { const promises: Promise[] = []; if (putSurveyData?.survey_details || putSurveyData?.purpose_and_methodology || putSurveyData?.location) { @@ -446,7 +442,7 @@ export class SurveyService extends DBService { } if (putSurveyData?.permit) { - promises.push(this.updateSurveyPermitData(projectId, surveyId, putSurveyData)); + promises.push(this.updateSurveyPermitData(surveyId, putSurveyData)); } if (putSurveyData?.funding) { @@ -463,10 +459,6 @@ export class SurveyService extends DBService { async updateSurveyDetailsData(surveyId: number, surveyData: PutSurveyObject) { const updateSurveyQueryBuilder = queries.survey.putSurveyDetailsSQL(surveyId, surveyData); - if (!updateSurveyQueryBuilder) { - throw new ApiGeneralError('Failed to build SQL update statement'); - } - const result = await this.connection.knex(updateSurveyQueryBuilder); if (!result || !result.rowCount) { @@ -497,31 +489,53 @@ export class SurveyService extends DBService { } /** - * To update a survey permit, we need to unassociate (not delete) the old permit from the survey and then associate - * the new permit to the survey. Associating a new permit to the survey is done by either inserting a brand new - * permit record into the permit table and setting the survey id column OR updating an existing permit record by - * setting the survey id column. + * Compares incoming survey permit data against the existing survey permits, if any, and determines which need to be + * deleted, added, or updated. * - * @param {number} projectId * @param {number} surveyId * @param {PutSurveyObject} surveyData - * @return {*} * @memberof SurveyService */ - async updateSurveyPermitData(projectId: number, surveyId: number, surveyData: PutSurveyObject) { - await this.unassociatePermitFromSurvey(surveyId); + async updateSurveyPermitData(surveyId: number, surveyData: PutSurveyObject) { + const permitService = new PermitService(this.connection); - if (!surveyData.permit.permit_number) { - return; + // Get any existing permits for this survey + const existingPermits = await permitService.getPermitBySurveyId(surveyId); + + // Compare the array of existing permits to the array of incoming permits (by permit id) and collect any + // existing permits that are not in the incoming permit array. + const existingPermitsToDelete = existingPermits.filter((existingPermit) => { + // Find all existing permits (by permit id) that have no matching incoming permit id + return !surveyData.permit.permits.find((incomingPermit) => incomingPermit.permit_id === existingPermit.permit_id); + }); + + // Delete from the database all existing survey permits that have been removed + if (existingPermitsToDelete.length) { + const promises: Promise[] = []; + + existingPermitsToDelete.forEach((permit) => { + promises.push(permitService.deleteSurveyPermit(surveyId, permit.permit_id)); + }); + + await Promise.all(promises); } - return this.insertOrAssociatePermitToSurvey( - this.connection.systemUserId() as number, - projectId, - surveyId, - surveyData.permit.permit_number, - surveyData.permit.permit_type - ); + // The remaining permits are either new, and can be created, or updates to existing permits + const promises: Promise[] = []; + + surveyData.permit.permits.forEach((permit) => { + if (permit.permit_id) { + // Has a permit_id, indicating this is an update to an existing permit + promises.push( + permitService.updateSurveyPermit(surveyId, permit.permit_id, permit.permit_number, permit.permit_type) + ); + } else { + // No permit_id, indicating this is a new permit which needs to be created + promises.push(permitService.createSurveyPermit(surveyId, permit.permit_number, permit.permit_type)); + } + }); + + return Promise.all(promises); } async unassociatePermitFromSurvey(surveyId: number) { @@ -587,24 +601,4 @@ export class SurveyService extends DBService { throw new ApiGeneralError('Failed to delete survey vantage codes'); } } - - /** - * Update a survey, marking it as published/unpublished. - * - * @param {number} surveyId - * @param {boolean} publish - * @return {*} {(Promise<{ id: number } | null>)} - * @memberof SurveyService - */ - async publishSurvey(surveyId: number, publish: boolean): Promise<{ id: number } | null> { - const sqlStatement = queries.survey.updateSurveyPublishStatusSQL(surveyId, publish); - - if (!sqlStatement) { - throw new ApiGeneralError('Failed to build survey publish SQL statement'); - } - - const response = await this.connection.sql<{ id: number }>(sqlStatement); - - return (response && response.rows && response.rows[0]) || null; - } } diff --git a/api/src/services/user-service.test.ts b/api/src/services/user-service.test.ts index 39fd18d5bc..a33f91ae28 100644 --- a/api/src/services/user-service.test.ts +++ b/api/src/services/user-service.test.ts @@ -543,23 +543,6 @@ describe('UserService', () => { } }); - it('throws an error if the query response has no rowCount', async function () { - const mockQueryResponse = ({ rowCount: 0 } as unknown) as QueryResult; - const mockDBConnection = getMockDBConnection({ systemUserId: () => 1, query: async () => mockQueryResponse }); - - const mockUsersByIdSQLResponse = SQL`Test SQL Statement`; - sinon.stub(queries.users, 'deleteAllSystemRolesSQL').returns(mockUsersByIdSQLResponse); - - const userService = new UserService(mockDBConnection); - - try { - await userService.deleteUserSystemRoles(1); - expect.fail(); - } catch (actualError) { - expect((actualError as ApiError).message).to.equal('Failed to delete user system roles'); - } - }); - it('returns nothing on success', async function () { const mockQueryResponse = ({ rowCount: 1 } as unknown) as QueryResult; const mockDBConnection = getMockDBConnection({ systemUserId: () => 1, query: async () => mockQueryResponse }); diff --git a/api/src/services/user-service.ts b/api/src/services/user-service.ts index 1967e1098c..1fe3549477 100644 --- a/api/src/services/user-service.ts +++ b/api/src/services/user-service.ts @@ -198,11 +198,7 @@ export class UserService extends DBService { throw new ApiBuildSQLError('Failed to build SQL delete statement'); } - const response = await this.connection.query(sqlStatement.text, sqlStatement.values); - - if (!response.rowCount) { - throw new ApiExecuteSQLError('Failed to delete user system roles'); - } + await this.connection.query(sqlStatement.text, sqlStatement.values); } /** diff --git a/api/src/utils/media/xlsx/xlsx-file.ts b/api/src/utils/media/xlsx/xlsx-file.ts index 28cd313346..0d94f1231a 100644 --- a/api/src/utils/media/xlsx/xlsx-file.ts +++ b/api/src/utils/media/xlsx/xlsx-file.ts @@ -45,7 +45,7 @@ export class XLSXCSV { const worksheet: CSVWorksheet = this.workbook.worksheets[fileName]; - if (!worksheet) { + if (!worksheet || fileName === 'Picklist Values') { return; } diff --git a/app/openshift/app.bc.yaml b/app/openshift/app.bc.yaml index 436a8fa7d0..b8a8c396bd 100644 --- a/app/openshift/app.bc.yaml +++ b/app/openshift/app.bc.yaml @@ -1,4 +1,4 @@ -apiVersion: v1 +apiVersion: template.openshift.io/v1 kind: Template metadata: creationTimestamp: null diff --git a/app/openshift/app.dc.yaml b/app/openshift/app.dc.yaml index c67ecd24e9..c7af3ad6a9 100644 --- a/app/openshift/app.dc.yaml +++ b/app/openshift/app.dc.yaml @@ -1,4 +1,4 @@ -apiVersion: v1 +apiVersion: template.openshift.io/v1 kind: Template metadata: resourceVersion: '' diff --git a/app/package-lock.json b/app/package-lock.json index 0da2e56ce1..0d18d82379 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -1765,15 +1765,27 @@ } }, "@material-ui/lab": { - "version": "4.0.0-alpha.60", - "resolved": "https://registry.npmjs.org/@material-ui/lab/-/lab-4.0.0-alpha.60.tgz", - "integrity": "sha512-fadlYsPJF+0fx2lRuyqAuJj7hAS1tLDdIEEdov5jlrpb5pp4b+mRDUqQTUxi4inRZHS1bEXpU8QWUhO6xX88aA==", + "version": "4.0.0-alpha.61", + "resolved": "https://registry.npmjs.org/@material-ui/lab/-/lab-4.0.0-alpha.61.tgz", + "integrity": "sha512-rSzm+XKiNUjKegj8bzt5+pygZeckNLOr+IjykH8sYdVk7dE9y2ZuUSofiMV2bJk3qU+JHwexmw+q0RyNZB9ugg==", "requires": { "@babel/runtime": "^7.4.4", - "@material-ui/utils": "^4.11.2", + "@material-ui/utils": "^4.11.3", "clsx": "^1.0.4", "prop-types": "^15.7.2", "react-is": "^16.8.0 || ^17.0.0" + }, + "dependencies": { + "@material-ui/utils": { + "version": "4.11.3", + "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.11.3.tgz", + "integrity": "sha512-ZuQPV4rBK/V1j2dIkSSEcH5uT6AaHuKWFfotADHsC0wVL1NLd2WkFCm4ZZbX33iO4ydl6V0GPngKm8HZQ2oujg==", + "requires": { + "@babel/runtime": "^7.4.4", + "prop-types": "^15.7.2", + "react-is": "^16.8.0 || ^17.0.0" + } + } } }, "@material-ui/pickers": { @@ -2165,6 +2177,16 @@ "@testing-library/dom": "^7.28.1" } }, + "@testing-library/react-hooks": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-8.0.1.tgz", + "integrity": "sha512-Aqhl2IVmLt8IovEVarNDFuJDVWVvhnr9/GCU6UUnrYXwgDFF9h2L2o2P9KBni1AST5sT6riAyoukFLyjQUgD/g==", + "dev": true, + "requires": { + "@babel/runtime": "^7.12.5", + "react-error-boundary": "^3.1.0" + } + }, "@testing-library/user-event": { "version": "12.6.0", "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-12.6.0.tgz", @@ -14084,6 +14106,15 @@ } } }, + "react-error-boundary": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.4.tgz", + "integrity": "sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.12.5" + } + }, "react-error-overlay": { "version": "6.0.8", "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.8.tgz", diff --git a/app/package.json b/app/package.json index 62060a2c0b..19d5b7fdad 100644 --- a/app/package.json +++ b/app/package.json @@ -73,6 +73,7 @@ "@babel/preset-typescript": "~7.12.7", "@testing-library/jest-dom": "~5.11.8", "@testing-library/react": "~11.2.3", + "@testing-library/react-hooks": "^8.0.1", "@testing-library/user-event": "~12.6.0", "@types/geojson": "~7946.0.7", "@types/jest": "~26.0.20", diff --git a/app/src/App.tsx b/app/src/App.tsx index f85c1abf97..e55f9a6cca 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -6,7 +6,7 @@ import { KeycloakProvider } from '@react-keycloak/web'; import AppRouter from 'AppRouter'; import { AuthStateContextProvider } from 'contexts/authStateContext'; import { ConfigContext, ConfigContextProvider } from 'contexts/configContext'; -import Keycloak, { KeycloakInstance } from 'keycloak-js'; +import Keycloak from 'keycloak-js'; import React from 'react'; import { BrowserRouter } from 'react-router-dom'; import appTheme from 'themes/appTheme'; @@ -21,11 +21,11 @@ const App: React.FC = () => { return ; } - //@ts-ignore - const keycloak: KeycloakInstance = new Keycloak(config.KEYCLOAK_CONFIG); + const keycloak = Keycloak(config.KEYCLOAK_CONFIG); return ( }> diff --git a/app/src/AppRouter.tsx b/app/src/AppRouter.tsx index bc339354ef..778565e5cb 100644 --- a/app/src/AppRouter.tsx +++ b/app/src/AppRouter.tsx @@ -5,7 +5,6 @@ import { } from 'components/security/RouteGuards'; import { SYSTEM_ROLE } from 'constants/roles'; import AdminUsersRouter from 'features/admin/AdminUsersRouter'; -import PermitsRouter from 'features/permits/PermitsRouter'; import ProjectsRouter from 'features/projects/ProjectsRouter'; import PublicProjectsRouter from 'features/projects/PublicProjectsRouter'; import ResourcesPage from 'features/resources/ResourcesPage'; @@ -85,12 +84,6 @@ const AppRouter: React.FC = () => { - - - - - - diff --git a/app/src/components/boundary/InferredLocationDetails.tsx b/app/src/components/boundary/InferredLocationDetails.tsx index 6be8c15280..699d10ff1c 100644 --- a/app/src/components/boundary/InferredLocationDetails.tsx +++ b/app/src/components/boundary/InferredLocationDetails.tsx @@ -1,7 +1,31 @@ import Box from '@material-ui/core/Box'; +import { Theme } from '@material-ui/core/styles/createMuiTheme'; +import makeStyles from '@material-ui/core/styles/makeStyles'; import Typography from '@material-ui/core/Typography'; import React from 'react'; +const useStyles = makeStyles((theme: Theme) => ({ + boundaryGroup: { + clear: 'both', + overflow: 'hidden', + '& + div': { + marginTop: theme.spacing(2) + } + }, + boundaryList: { + margin: 0, + padding: 0, + listStyleType: 'none', + '& li': { + display: 'inline-block', + float: 'left' + }, + '& li + li': { + marginLeft: theme.spacing(1) + } + } +})); + export interface IInferredLayers { parks: string[]; nrm: string[]; @@ -14,23 +38,25 @@ export interface IInferredLocationDetailsProps { } const InferredLocationDetails: React.FC = (props) => { + const classes = useStyles(); const displayInferredLayersInfo = (data: any[], type: string) => { if (!data.length) { return; } return ( - + {type} ({data.length}) - - {data.map((item: string, index: number) => ( - - {item} - {index < data.length - 1 && ', '} - - ))} + + {data.map((item: string, index: number) => ( + + {item} + {index < data.length - 1 && ', '} + + ))} + ); }; @@ -38,8 +64,8 @@ const InferredLocationDetails: React.FC = (props) return ( <> {displayInferredLayersInfo(props.layers.nrm, 'Natural Resource Ministries Regions')} - {displayInferredLayersInfo(props.layers.env, 'Ministry of Environment Regions')} - {displayInferredLayersInfo(props.layers.parks, 'Parks and EcoReserves')} + {displayInferredLayersInfo(props.layers.env, 'Ministry of Environment Regions')} + {displayInferredLayersInfo(props.layers.parks, 'Parks and EcoReserves')} ); }; diff --git a/app/src/components/boundary/MapBoundary.tsx b/app/src/components/boundary/MapBoundary.tsx index f80337023d..8ec252a48d 100644 --- a/app/src/components/boundary/MapBoundary.tsx +++ b/app/src/components/boundary/MapBoundary.tsx @@ -5,6 +5,7 @@ import Grid from '@material-ui/core/Grid'; import IconButton from '@material-ui/core/IconButton'; import InputLabel from '@material-ui/core/InputLabel'; import MenuItem from '@material-ui/core/MenuItem'; +import Paper from '@material-ui/core/Paper'; import Select from '@material-ui/core/Select'; import { createStyles, makeStyles } from '@material-ui/core/styles'; import Typography from '@material-ui/core/Typography'; @@ -41,17 +42,10 @@ const useStyles = makeStyles(() => backgroundColor: '#eeeeee' } }, - bold: { - fontWeight: 'bold' - }, - uploadButton: { - border: '2px solid', - textTransform: 'capitalize', - fontWeight: 'bold' - }, - mapLocations: { - '& dd': { - display: 'inline-block' + mapLayerControl: { + width: '300px', + '& .MuiInputBase-root': { + height: '44px' } } }) @@ -111,11 +105,11 @@ const MapBoundary: React.FC = (props) => { <> setOpenUploadBoundary(false)}> - If uploading a shapefile, it must be configured with a valid projection. + If importing a shapefile, it must be configured with a valid projection. = (props) => { - {title} - - - Define your boundary by selecting a boundary from an existing layer or by uploading KML file or shapefile. - - - - To select a boundary from an existing layer, select a layer from the dropdown, click a boundary on the map - and click 'Add Boundary'. - - - - - - - - Select Layer - - - - {selectedLayer && ( + + {title} + + + Import or select a boundary from existing map layers. To select an existing boundary, choose a map layer below + and click a boundary on the map. + + + - )} - - - {get(errors, name) && {get(errors, name)}} - - - setFieldValue(name, newGeo) - }} - bounds={(shouldUpdateBounds && updatedBounds) || bounds} - selectedLayer={selectedLayer} - setInferredLayersInfo={setInferredLayersInfo} - /> - {get(values, name) && get(values, name).length > 0 && ( - - { - setUpdatedBounds(calculateUpdatedMapBounds(get(values, name))); - setShouldUpdateBounds(true); - }}> - - + + + Map Layers + + + + + {selectedLayer && ( + + )} + + + {get(errors, name) && ( + + {get(errors, name)} )} - {get(errors, name) && ( - - {get(errors, name)} + + + setFieldValue(name, newGeo) + }} + bounds={(shouldUpdateBounds && updatedBounds) || bounds} + selectedLayer={selectedLayer} + setInferredLayersInfo={setInferredLayersInfo} + /> + {get(values, name) && get(values, name).length > 0 && ( + + { + setUpdatedBounds(calculateUpdatedMapBounds(get(values, name))); + setShouldUpdateBounds(true); + }}> + + + + )} - )} - {!Object.values(inferredLayersInfo).every((item: any) => !item.length) && ( - <> - - Boundary Information - -
+ {!Object.values(inferredLayersInfo).every((item: any) => !item.length) && ( + -
- - )} +
+ )} +
); diff --git a/app/src/components/dialog/YesNoDialog.tsx b/app/src/components/dialog/YesNoDialog.tsx index b899d87d84..6f150eddf8 100644 --- a/app/src/components/dialog/YesNoDialog.tsx +++ b/app/src/components/dialog/YesNoDialog.tsx @@ -101,6 +101,7 @@ const YesNoDialog: React.FC = (props) => { return ( - -
-
- -
- - -
-
-
-
-
- -
- - -
-
-
-
- -
- -
-
- - Share Contact Details - -

- Do you want the project contact’s name and email address visible to the public? -

-
-
- - -

-

-
-
-
- - - -
-
-
-

- Non-Sampling Permits -

-
-

- Enter any scientific collection, wildlife act and/or park use permits. Provide the last 6 digits of the permit number. The last 6 digits are those after the hyphen (e.g. for KA12-845782 enter 845782). -

-
-
-
-
-
-
-
-
-
-
- -
- - -
-
-
-
-
- -
-
- - ​ - -
- - - -
-

-

-
-
- -
-
-
-
-
- -
-
-
-
-
-
-
- - -
- - - - -`; - -exports[`CreatePermitPage shows circular spinner when codes not yet loaded 1`] = ` - -
- - - -
-
-`; diff --git a/app/src/features/permits/__snapshots__/PermitsPage.test.tsx.snap b/app/src/features/permits/__snapshots__/PermitsPage.test.tsx.snap deleted file mode 100644 index ebb1dbfa0b..0000000000 --- a/app/src/features/permits/__snapshots__/PermitsPage.test.tsx.snap +++ /dev/null @@ -1,164 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`PermitsPage renders with a proper list of permits 1`] = ` - -
-
-
-

- Permits -

- -
-
-
-

- 2 Permits found -

-
-
-
- - - - - - - - - - - - - - - - - - - - - - - -
- Number - - Type - - Contact Agency - - Associated Project -
- 123 - - Wildlife - - contact agency - - Project 1 -
- 1234 - - Wildlife - - contact agency 2 - - No Associated Project -
-
-
-
-
-
-`; diff --git a/app/src/features/projects/components/ProjectCoordinatorForm.test.tsx b/app/src/features/projects/components/ProjectCoordinatorForm.test.tsx index ef390e28c3..0df89f229b 100644 --- a/app/src/features/projects/components/ProjectCoordinatorForm.test.tsx +++ b/app/src/features/projects/components/ProjectCoordinatorForm.test.tsx @@ -1,4 +1,4 @@ -import { render } from '@testing-library/react'; +import { render, waitFor } from '@testing-library/react'; import ProjectCoordinatorForm, { ProjectCoordinatorInitialValues, ProjectCoordinatorYupSchema @@ -11,16 +11,18 @@ const handleSaveAndNext = jest.fn(); const agencies = ['Agency 1', 'Agency 2', 'Agency 3']; const projectCoordinatorFilledValues = { - first_name: 'Nerea', - last_name: 'Oneal', - email_address: 'quxu@mailinator.com', - coordinator_agency: 'Agency 3', - share_contact_details: 'true' + coordinator: { + first_name: 'Nerea', + last_name: 'Oneal', + email_address: 'quxu@mailinator.com', + coordinator_agency: 'Agency 3', + share_contact_details: 'true' + } }; describe('Project Contact Form', () => { - it('renders correctly the empty component correctly', () => { - const { asFragment } = render( + it('renders correctly the empty component correctly', async () => { + const { getByLabelText } = render( { ); - expect(asFragment()).toMatchSnapshot(); + await waitFor(() => { + expect(getByLabelText('First Name', { exact: false })).toBeVisible(); + }); }); - it('renders correctly the filled component correctly', () => { - const { asFragment } = render( + it('renders correctly the filled component correctly', async () => { + const { getByLabelText, getByDisplayValue } = render( { ); - expect(asFragment()).toMatchSnapshot(); + await waitFor(() => { + expect(getByLabelText('First Name', { exact: false })).toBeVisible(); + expect(getByDisplayValue('Nerea', { exact: false })).toBeVisible(); + }); }); }); diff --git a/app/src/features/projects/components/ProjectCoordinatorForm.tsx b/app/src/features/projects/components/ProjectCoordinatorForm.tsx index f3738b4904..21ff995135 100644 --- a/app/src/features/projects/components/ProjectCoordinatorForm.tsx +++ b/app/src/features/projects/components/ProjectCoordinatorForm.tsx @@ -5,71 +5,70 @@ import FormHelperText from '@material-ui/core/FormHelperText'; import Grid from '@material-ui/core/Grid'; import Radio from '@material-ui/core/Radio'; import RadioGroup from '@material-ui/core/RadioGroup'; -import { Theme } from '@material-ui/core/styles/createMuiTheme'; -import makeStyles from '@material-ui/core/styles/makeStyles'; import Typography from '@material-ui/core/Typography'; import AutocompleteFreeSoloField from 'components/fields/AutocompleteFreeSoloField'; import CustomTextField from 'components/fields/CustomTextField'; import { useFormikContext } from 'formik'; +import { ICreateProjectRequest } from 'interfaces/useProjectApi.interface'; import React from 'react'; import yup from 'utils/YupSchema'; export interface IProjectCoordinatorForm { - first_name: string; - last_name: string; - email_address: string; - coordinator_agency: string; - share_contact_details: string; + coordinator: { + first_name: string; + last_name: string; + email_address: string; + coordinator_agency: string; + share_contact_details: string; + }; } export const ProjectCoordinatorInitialValues: IProjectCoordinatorForm = { - first_name: '', - last_name: '', - email_address: '', - coordinator_agency: '', - share_contact_details: 'false' + coordinator: { + first_name: '', + last_name: '', + email_address: '', + coordinator_agency: '', + share_contact_details: 'false' + } }; export const ProjectCoordinatorYupSchema = yup.object().shape({ - first_name: yup.string().max(50, 'Cannot exceed 50 characters').required('Required'), - last_name: yup.string().max(50, 'Cannot exceed 50 characters').required('Required'), - email_address: yup - .string() - .max(500, 'Cannot exceed 500 characters') - .email('Must be a valid email address') - .required('Required'), - coordinator_agency: yup.string().max(300, 'Cannot exceed 300 characters').required('Required').nullable(), - share_contact_details: yup.string().required('Required') + coordinator: yup.object().shape({ + first_name: yup.string().max(50, 'Cannot exceed 50 characters').required('First Name is Required'), + last_name: yup.string().max(50, 'Cannot exceed 50 characters').required('Last Name is Required'), + email_address: yup + .string() + .max(500, 'Cannot exceed 500 characters') + .email('Must be a valid email address') + .required('Business Email Address is Required'), + coordinator_agency: yup + .string() + .max(300, 'Cannot exceed 300 characters') + .required('Coordinator Agency is Required') + .nullable(), + share_contact_details: yup.string().required('Please select an option') + }) }); export interface IProjectCoordinatorFormProps { coordinator_agency: string[]; } -const useStyles = makeStyles((theme: Theme) => ({ - legend: { - marginTop: '1rem', - float: 'left', - marginBottom: '0.75rem', - letterSpacing: '-0.01rem' - } -})); - /** * Create project - coordinator fields * * @return {*} */ const ProjectCoordinatorForm: React.FC = (props) => { - const classes = useStyles(); - const { values, touched, errors, handleChange, handleSubmit } = useFormikContext(); + const { values, touched, handleSubmit, errors, handleChange } = useFormikContext(); return (
= (props) = = (props) = = (props) = - + - + error={touched.coordinator?.share_contact_details && Boolean(errors.coordinator?.share_contact_details)}> + Share Contact Details - + - Do you want the project contact’s name and email address visible to the public? + Do you want the project contact's name and email address visible to the public? = (props) = control={} label="Yes" /> - {errors.share_contact_details} + {errors.coordinator?.share_contact_details} diff --git a/app/src/features/projects/components/ProjectDetailsForm.test.tsx b/app/src/features/projects/components/ProjectDetailsForm.test.tsx index 80ee1e1453..2c62221da0 100644 --- a/app/src/features/projects/components/ProjectDetailsForm.test.tsx +++ b/app/src/features/projects/components/ProjectDetailsForm.test.tsx @@ -1,4 +1,4 @@ -import { render } from '@testing-library/react'; +import { render, waitFor } from '@testing-library/react'; import { IMultiAutocompleteFieldOption } from 'components/fields/MultiAutocompleteFieldVariableSize'; import { Formik } from 'formik'; import React from 'react'; @@ -39,8 +39,8 @@ const activity: IMultiAutocompleteFieldOption[] = [ ]; describe('ProjectDetailsForm', () => { - it('renders correctly with default empty values', () => { - const { asFragment } = render( + it('renders correctly with default empty values', async () => { + const { getByLabelText } = render( { ); - expect(asFragment()).toMatchSnapshot(); + await waitFor(() => { + expect(getByLabelText('Project Name', { exact: false })).toBeVisible(); + }); }); - it('renders correctly with existing details values', () => { + it('renders correctly with existing details values', async () => { const existingFormValues: IProjectDetailsForm = { - project_name: 'name 1', - project_type: 2, - project_activities: [2, 3], - start_date: '2021-03-14', - end_date: '2021-04-14' + project: { + project_name: 'name 1', + project_type: 2, + project_activities: [2, 3], + start_date: '2021-03-14', + end_date: '2021-04-14' + } }; - const { asFragment } = render( + const { getByLabelText, getByText } = render( { ); - expect(asFragment()).toMatchSnapshot(); + await waitFor(() => { + expect(getByLabelText('Project Name', { exact: false })).toBeVisible(); + expect(getByText('type 2', { exact: false })).toBeVisible(); + }); }); }); diff --git a/app/src/features/projects/components/ProjectDetailsForm.tsx b/app/src/features/projects/components/ProjectDetailsForm.tsx index ce1fa187f2..a93ef3c1eb 100644 --- a/app/src/features/projects/components/ProjectDetailsForm.tsx +++ b/app/src/features/projects/components/ProjectDetailsForm.tsx @@ -10,30 +10,37 @@ import MultiAutocompleteFieldVariableSize, { } from 'components/fields/MultiAutocompleteFieldVariableSize'; import StartEndDateFields from 'components/fields/StartEndDateFields'; import { useFormikContext } from 'formik'; +import { ICreateProjectRequest } from 'interfaces/useProjectApi.interface'; import React from 'react'; import yup from 'utils/YupSchema'; export interface IProjectDetailsForm { - project_name: string; - project_type: number; - project_activities: number[]; - start_date: string; - end_date: string; + project: { + project_name: string; + project_type: number; + project_activities: number[]; + start_date: string; + end_date: string; + }; } export const ProjectDetailsFormInitialValues: IProjectDetailsForm = { - project_name: '', - project_type: ('' as unknown) as number, - project_activities: [], - start_date: '', - end_date: '' + project: { + project_name: '', + project_type: ('' as unknown) as number, + project_activities: [], + start_date: '', + end_date: '' + } }; export const ProjectDetailsFormYupSchema = yup.object().shape({ - project_name: yup.string().max(300, 'Cannot exceed 300 characters').required('Required'), - project_type: yup.number().required('Required'), - start_date: yup.string().isValidDateString().required('Required'), - end_date: yup.string().isValidDateString().isEndDateSameOrAfterStartDate('start_date') + project: yup.object().shape({ + project_name: yup.string().max(300, 'Cannot exceed 300 characters').required('Project Name is Required'), + project_type: yup.number().required('Project Type is Required'), + start_date: yup.string().isValidDateString().required('Start Date is Required'), + end_date: yup.string().isValidDateString().isEndDateSameOrAfterStartDate('start_date') + }) }); export interface IProjectDetailsFormProps { @@ -47,7 +54,7 @@ export interface IProjectDetailsFormProps { * @return {*} */ const ProjectDetailsForm: React.FC = (props) => { - const formikProps = useFormikContext(); + const formikProps = useFormikContext(); const { values, touched, errors, handleChange, handleSubmit } = formikProps; @@ -56,7 +63,7 @@ const ProjectDetailsForm: React.FC = (props) => { = (props) => { /> - + Project Type - {touched.project_type && errors.project_type} + {errors.project?.project_type && ( + {touched.project?.project_type && errors.project?.project_type} + )} - + + + ); diff --git a/app/src/features/projects/components/ProjectDraftForm.test.tsx b/app/src/features/projects/components/ProjectDraftForm.test.tsx index 1b88b4fe9c..ca1841e96c 100644 --- a/app/src/features/projects/components/ProjectDraftForm.test.tsx +++ b/app/src/features/projects/components/ProjectDraftForm.test.tsx @@ -1,5 +1,6 @@ -import { render } from '@testing-library/react'; +import { render, waitFor } from '@testing-library/react'; import ProjectDraftForm, { + IProjectDraftForm, ProjectDraftFormInitialValues, ProjectDraftFormYupSchema } from 'features/projects/components/ProjectDraftForm'; @@ -8,13 +9,9 @@ import React from 'react'; const handleSaveAndNext = jest.fn(); -const projectDraftFilledValues = { - draft_name: 'draft test name' -}; - describe('Project Draft Form', () => { - it('renders correctly with empty initial values', () => { - const { asFragment } = render( + it('renders correctly with empty initial values', async () => { + const { getByLabelText } = render( { ); - expect(asFragment()).toMatchSnapshot(); + await waitFor(() => { + expect(getByLabelText('Draft Name', { exact: false })).toBeVisible(); + }); }); - it('renders correctly with populated initial values', () => { - const { asFragment } = render( + it('renders correctly with populated initial values', async () => { + const projectDraftFilledValues: IProjectDraftForm = { + draft_name: 'draft test name' + }; + + const { getByLabelText, getByDisplayValue } = render( { - handleSaveAndNext(values); - }}> + onSubmit={async () => {}}> {() => } ); - expect(asFragment()).toMatchSnapshot(); + await waitFor(() => { + expect(getByLabelText('Draft Name', { exact: false })).toBeVisible(); + expect(getByDisplayValue('draft test name', { exact: false })).toBeVisible(); + }); }); - it('renders correctly with errors', () => { - const { asFragment } = render( + it('renders correctly with errors', async () => { + const projectDraftFilledValues: IProjectDraftForm = { + draft_name: 'draft test name' + }; + + const { getByLabelText, getByText } = render( { ); - expect(asFragment()).toMatchSnapshot(); + await waitFor(() => { + expect(getByLabelText('Draft Name', { exact: false })).toBeVisible(); + expect(getByText('Error this is a required field', { exact: false })).toBeVisible(); + }); }); }); diff --git a/app/src/features/projects/components/ProjectFundingForm.test.tsx b/app/src/features/projects/components/ProjectFundingForm.test.tsx index 456dc4d124..3a5a376287 100644 --- a/app/src/features/projects/components/ProjectFundingForm.test.tsx +++ b/app/src/features/projects/components/ProjectFundingForm.test.tsx @@ -45,8 +45,8 @@ const investment_action_category: IInvestmentActionCategoryOption[] = [ ]; describe('ProjectFundingForm', () => { - it('renders correctly with default empty values', () => { - const { baseElement } = render( + it('renders correctly with default empty values', async () => { + const { queryByText } = render( { ); - expect(baseElement).toMatchSnapshot(); + await waitFor(() => { + expect(queryByText('Add Funding Source')).toBeInTheDocument(); + }); }); - it('renders correctly with existing funding values', () => { + it('renders correctly with existing funding values', async () => { const existingFormValues: IProjectFundingForm = { - funding_sources: [ - { - id: 11, - agency_id: 1, - investment_action_category: 1, - investment_action_category_name: 'Action 23', - agency_project_id: '111', - funding_amount: 222, - start_date: '2021-03-14', - end_date: '2021-04-14', - revision_count: 23 - } - ] + funding: { + funding_sources: [ + { + id: 11, + agency_id: 1, + investment_action_category: 1, + investment_action_category_name: 'Action 23', + agency_project_id: '111', + funding_amount: 222, + start_date: '2021-03-14', + end_date: '2021-04-14', + revision_count: 23 + } + ] + } }; - const { baseElement } = render( + const { queryByText } = render( { ); - expect(baseElement).toMatchSnapshot(); + await waitFor(() => { + expect(queryByText('Add Funding Source')).toBeInTheDocument(); + expect(queryByText('111')).toBeInTheDocument(); + }); }); it('shows add funding source dialog on add click', async () => { const existingFormValues: IProjectFundingForm = { - funding_sources: [ - { - id: 11, - agency_id: 1, - investment_action_category: 1, - investment_action_category_name: 'action 1', - agency_project_id: '111', - funding_amount: 222, - start_date: '2021-03-14', - end_date: '2021-04-14', - revision_count: 23 - }, - { - id: 12, - agency_id: 2, - investment_action_category: 2, - investment_action_category_name: 'category 1', - agency_project_id: '112', - funding_amount: 223, - start_date: '2021-03-15', - end_date: '2021-04-15', - revision_count: 24 - } - ] + funding: { + funding_sources: [ + { + id: 11, + agency_id: 1, + investment_action_category: 1, + investment_action_category_name: 'action 1', + agency_project_id: '111', + funding_amount: 222, + start_date: '2021-03-14', + end_date: '2021-04-14', + revision_count: 23 + }, + { + id: 12, + agency_id: 2, + investment_action_category: 2, + investment_action_category_name: 'category 1', + agency_project_id: '112', + funding_amount: 223, + start_date: '2021-03-15', + end_date: '2021-04-15', + revision_count: 24 + } + ] + } }; const { getByTestId, queryByText } = render( @@ -149,19 +158,21 @@ describe('ProjectFundingForm', () => { it('shows edit funding source dialog on edit click', async () => { await act(async () => { const existingFormValues: IProjectFundingForm = { - funding_sources: [ - { - id: 11, - agency_id: 1, - investment_action_category: 1, - investment_action_category_name: 'action 1', - agency_project_id: '111', - funding_amount: 222, - start_date: '2021-03-14', - end_date: '2021-04-14', - revision_count: 23 - } - ] + funding: { + funding_sources: [ + { + id: 11, + agency_id: 1, + investment_action_category: 1, + investment_action_category_name: 'action 1', + agency_project_id: '111', + funding_amount: 222, + start_date: '2021-03-14', + end_date: '2021-04-14', + revision_count: 23 + } + ] + } }; const { getByTestId, getByText, queryByText } = render( @@ -197,19 +208,21 @@ describe('ProjectFundingForm', () => { it('deletes funding source dialog on delete click', async () => { await act(async () => { const existingFormValues: IProjectFundingForm = { - funding_sources: [ - { - id: 11, - agency_id: 1, - investment_action_category: 1, - investment_action_category_name: 'action 1', - agency_project_id: '111', - funding_amount: 222, - start_date: '2021-03-14', - end_date: '2021-04-14', - revision_count: 23 - } - ] + funding: { + funding_sources: [ + { + id: 11, + agency_id: 1, + investment_action_category: 1, + investment_action_category_name: 'action 1', + agency_project_id: '111', + funding_amount: 222, + start_date: '2021-03-14', + end_date: '2021-04-14', + revision_count: 23 + } + ] + } }; const { getByTestId, queryByTestId } = render( diff --git a/app/src/features/projects/components/ProjectFundingForm.tsx b/app/src/features/projects/components/ProjectFundingForm.tsx index a0b5336368..9bfb1ba837 100644 --- a/app/src/features/projects/components/ProjectFundingForm.tsx +++ b/app/src/features/projects/components/ProjectFundingForm.tsx @@ -1,11 +1,10 @@ import Box from '@material-ui/core/Box'; import Button from '@material-ui/core/Button'; -import Divider from '@material-ui/core/Divider'; +import { grey } from '@material-ui/core/colors'; import Grid from '@material-ui/core/Grid'; import IconButton from '@material-ui/core/IconButton'; import List from '@material-ui/core/List'; import ListItem from '@material-ui/core/ListItem'; -import Paper from '@material-ui/core/Paper'; import { Theme } from '@material-ui/core/styles/createMuiTheme'; import makeStyles from '@material-ui/core/styles/makeStyles'; import Toolbar from '@material-ui/core/Toolbar'; @@ -17,6 +16,7 @@ import { IMultiAutocompleteFieldOption } from 'components/fields/MultiAutocomple import { DATE_FORMAT } from 'constants/dateTimeFormats'; import { AddFundingI18N } from 'constants/i18n'; import { FieldArray, useFormikContext } from 'formik'; +import { ICreateProjectRequest } from 'interfaces/useProjectApi.interface'; import React, { useState } from 'react'; import { getFormattedAmount, getFormattedDateRangeString } from 'utils/Utils'; import yup from 'utils/YupSchema'; @@ -27,11 +27,15 @@ import ProjectFundingItemForm, { } from './ProjectFundingItemForm'; export interface IProjectFundingForm { - funding_sources: IProjectFundingFormArrayItem[]; + funding: { + funding_sources: IProjectFundingFormArrayItem[]; + }; } export const ProjectFundingFormInitialValues: IProjectFundingForm = { - funding_sources: [] + funding: { + funding_sources: [] + } }; export const ProjectFundingFormYupSchema = yup.object().shape({}); @@ -58,19 +62,23 @@ const useStyles = makeStyles((theme: Theme) => ({ marginLeft: theme.spacing(1), fontWeight: 400 }, - fundingListIem: { + fundingListItem: { + flexDirection: 'column', + alignItems: 'normal', padding: 0, - '& + li': { - marginTop: theme.spacing(2) - } - }, - fundingListItemInner: { - flexGrow: 1, - flexShrink: 1, - overflow: 'hidden' + marginTop: theme.spacing(2), + borderStyle: 'solid', + borderWidth: '1px', + borderColor: grey[400], + borderRadius: '4px' }, fundingListItemToolbar: { - paddingRight: theme.spacing(2) + minHeight: '60px', + paddingLeft: theme.spacing(2), + paddingRight: theme.spacing(2), + backgroundColor: grey[100], + borderTopLeftRadius: '4px', + borderTopRightRadius: '4px' } })); @@ -82,8 +90,8 @@ const useStyles = makeStyles((theme: Theme) => ({ const ProjectFundingForm: React.FC = (props) => { const classes = useStyles(); - const formikProps = useFormikContext(); - const { values } = formikProps; + const formikProps = useFormikContext(); + const { values, handleSubmit } = formikProps; //Tracks information about the current funding source item that is being added/edited const [currentProjectFundingFormArrayItem, setCurrentProjectFundingFormArrayItem] = useState({ @@ -94,30 +102,27 @@ const ProjectFundingForm: React.FC = (props) => { const [isModalOpen, setIsModalOpen] = useState(false); return ( -
+ - - Funding Sources ({values.funding_sources.length}) - - + ( = (props) => { }} onCancel={() => setIsModalOpen(false)} onSave={(projectFundingItemValues) => { - if (currentProjectFundingFormArrayItem.index < values.funding_sources.length) { + if (currentProjectFundingFormArrayItem.index < values.funding.funding_sources.length) { // Update an existing item arrayHelpers.replace(currentProjectFundingFormArrayItem.index, projectFundingItemValues); } else { @@ -148,14 +153,7 @@ const ProjectFundingForm: React.FC = (props) => { }} /> - {!values.funding_sources.length && ( - - - No Funding Sources - - - )} - {values.funding_sources.map((fundingSource, index) => { + {values.funding.funding_sources.map((fundingSource, index) => { const investment_action_category_label = (fundingSource.agency_id === 1 && 'Investment Action') || (fundingSource.agency_id === 2 && 'Investment Category') || @@ -166,71 +164,65 @@ const ProjectFundingForm: React.FC = (props) => { )?.[0]?.label; return ( - - - - - {getCodeValueNameByID(props.funding_sources, fundingSource.agency_id)} - {investment_action_category_label && ( - ({investment_action_category_value}) - )} - - - { - setCurrentProjectFundingFormArrayItem({ - index: index, - values: values.funding_sources[index] - }); - setIsModalOpen(true); - }}> - - - arrayHelpers.remove(index)}> - - - - - - - - - Agency Project ID - - {fundingSource.agency_project_id} - - - - Funding Amount - - - {getFormattedAmount(fundingSource.funding_amount)} - - - - - Start / End Date - - - {getFormattedDateRangeString( - DATE_FORMAT.ShortMediumDateFormat, - fundingSource.start_date, - fundingSource.end_date - )} - - + + + + {getCodeValueNameByID(props.funding_sources, fundingSource.agency_id)} + {investment_action_category_label && ( + ({investment_action_category_value}) + )} + + { + setCurrentProjectFundingFormArrayItem({ + index: index, + values: values.funding.funding_sources[index] + }); + setIsModalOpen(true); + }}> + + + arrayHelpers.remove(index)}> + + + + + + + + Agency Project ID + + {fundingSource.agency_project_id} + + + + Funding Amount + + + {getFormattedAmount(fundingSource.funding_amount)} + + + + + Start / End Date + + + {getFormattedDateRangeString( + DATE_FORMAT.ShortMediumDateFormat, + fundingSource.start_date, + fundingSource.end_date + )} + - - + + ); })} diff --git a/app/src/features/projects/components/ProjectIUCNForm.test.tsx b/app/src/features/projects/components/ProjectIUCNForm.test.tsx index d2f000cbe1..e96d75d4b5 100644 --- a/app/src/features/projects/components/ProjectIUCNForm.test.tsx +++ b/app/src/features/projects/components/ProjectIUCNForm.test.tsx @@ -49,7 +49,7 @@ const subClassifications2: IIUCNSubClassification2Option[] = [ describe('ProjectIUCNForm', () => { it('renders correctly with default empty values', () => { - const { asFragment } = render( + const { queryByLabelText } = render( { )} ); - - expect(asFragment()).toMatchSnapshot(); + expect(queryByLabelText('Classification')).toBe(null); + expect(queryByLabelText('Sub-classification')).toBe(null); }); it('renders correctly with existing details values', () => { const existingFormValues: IProjectIUCNForm = { - classificationDetails: [ - { - classification: 1, - subClassification1: 3, - subClassification2: 5 - } - ] + iucn: { + classificationDetails: [ + { + classification: 1, + subClassification1: 3, + subClassification2: 5 + } + ] + } }; - const { asFragment } = render( + const { getByLabelText, getByText, getAllByLabelText } = render( { ); - expect(asFragment()).toMatchSnapshot(); + expect(getByLabelText('Classification')).toBeVisible(); + expect(getAllByLabelText('Sub-classification').length).toEqual(2); + expect(getByText('Class 1')).toBeVisible(); + expect(getByText('A Sub-class 1', { exact: false })).toBeVisible(); + expect(getByText('A Sub-class 2', { exact: false })).toBeVisible(); }); it('changes fields on the IUCN menu items as expected', async () => { - const { asFragment, getAllByRole, getByRole, queryByTestId, getByText } = render( + const { getByLabelText, getAllByRole, getByRole, queryByTestId, getByText, getAllByLabelText } = render( { fireEvent.click(subClassification2Listbox.getByText(/A Sub-class 2/i)); await waitFor(() => { - expect(asFragment()).toMatchSnapshot(); + expect(getByLabelText('Classification')).toBeVisible(); + expect(getAllByLabelText('Sub-classification').length).toEqual(2); + expect(getByText('Class 1')).toBeVisible(); + expect(getByText('A Sub-class 1', { exact: false })).toBeVisible(); + expect(getByText('A Sub-class 2', { exact: false })).toBeVisible(); }); }); @@ -172,18 +182,20 @@ describe('ProjectIUCNForm', () => { it('renders correctly with error on the iucn classifications due to duplicates', () => { const existingFormValues: IProjectIUCNForm = { - classificationDetails: [ - { - classification: 1, - subClassification1: 3, - subClassification2: 5 - }, - { - classification: 1, - subClassification1: 3, - subClassification2: 5 - } - ] + iucn: { + classificationDetails: [ + { + classification: 1, + subClassification1: 3, + subClassification2: 5 + }, + { + classification: 1, + subClassification1: 3, + subClassification2: 5 + } + ] + } }; const { getAllByText, getByText } = render( @@ -192,7 +204,7 @@ describe('ProjectIUCNForm', () => { validationSchema={ProjectIUCNFormYupSchema} validateOnBlur={true} validateOnChange={false} - initialErrors={{ classificationDetails: 'Error is here' }} + initialErrors={{ iucn: { classificationDetails: 'Error is here' } }} onSubmit={async () => {}}> {() => ( { expect(getByText('Error is here')).toBeVisible(); }); - it('renders correctly with error on the iucn classification individual fields', () => { - const existingFormValues: IProjectIUCNForm = { - classificationDetails: [ - { - classification: 1, - subClassification1: 3, - subClassification2: 5 - } - ] - }; - - const { getByText } = render( - {}}> - {() => ( - - )} - - ); - - expect(getByText('Class 1')).toBeVisible(); - expect(getByText('A Sub-class 1')).toBeVisible(); - expect(getByText('A Sub-class 2')).toBeVisible(); - expect(getByText('Error here')).toBeVisible(); - expect(getByText('Error here too')).toBeVisible(); - expect(getByText('Error again here too')).toBeVisible(); - }); - it('deletes existing iucn classifications when delete icon is clicked', async () => { const existingFormValues: IProjectIUCNForm = { - classificationDetails: [ - { - classification: 1, - subClassification1: 3, - subClassification2: 5 - } - ] + iucn: { + classificationDetails: [ + { + classification: 1, + subClassification1: 3, + subClassification2: 5 + } + ] + } }; const { getByTestId, queryByTestId } = render( diff --git a/app/src/features/projects/components/ProjectIUCNForm.tsx b/app/src/features/projects/components/ProjectIUCNForm.tsx index f8d193115a..552a096ea2 100644 --- a/app/src/features/projects/components/ProjectIUCNForm.tsx +++ b/app/src/features/projects/components/ProjectIUCNForm.tsx @@ -6,8 +6,6 @@ import IconButton from '@material-ui/core/IconButton'; import InputLabel from '@material-ui/core/InputLabel'; import MenuItem from '@material-ui/core/MenuItem'; import Select from '@material-ui/core/Select'; -import { Theme } from '@material-ui/core/styles/createMuiTheme'; -import makeStyles from '@material-ui/core/styles/makeStyles'; import Typography from '@material-ui/core/Typography'; import { mdiArrowRight, mdiPlus, mdiTrashCanOutline } from '@mdi/js'; import Icon from '@mdi/react'; @@ -16,15 +14,6 @@ import { FieldArray, useFormikContext } from 'formik'; import React from 'react'; import yup from 'utils/YupSchema'; -const useStyles = makeStyles((theme: Theme) => ({ - iucnInputContainer: { - overflow: 'hidden' - }, - iucnInput: { - width: '250px' - } -})); - export interface IProjectIUCNFormArrayItem { classification: number; subClassification1: number; @@ -32,7 +21,9 @@ export interface IProjectIUCNFormArrayItem { } export interface IProjectIUCNForm { - classificationDetails: IProjectIUCNFormArrayItem[]; + iucn: { + classificationDetails: IProjectIUCNFormArrayItem[]; + }; } export const ProjectIUCNFormArrayItemInitialValues: IProjectIUCNFormArrayItem = { @@ -42,7 +33,9 @@ export const ProjectIUCNFormArrayItemInitialValues: IProjectIUCNFormArrayItem = }; export const ProjectIUCNFormInitialValues: IProjectIUCNForm = { - classificationDetails: [] + iucn: { + classificationDetails: [] + } }; export interface IIUCNSubClassification1Option extends IMultiAutocompleteFieldOption { @@ -54,16 +47,18 @@ export interface IIUCNSubClassification2Option extends IMultiAutocompleteFieldOp } export const ProjectIUCNFormYupSchema = yup.object().shape({ - classificationDetails: yup - .array() - .of( - yup.object().shape({ - classification: yup.number().required('You must specify a classification'), - subClassification1: yup.number().required('You must specify a sub-classification'), - subClassification2: yup.number().required('You must specify a sub-classification') - }) - ) - .isUniqueIUCNClassificationDetail('IUCN Classifications must be unique') + iucn: yup.object().shape({ + classificationDetails: yup + .array() + .of( + yup.object().shape({ + classification: yup.number().required('You must specify a classification'), + subClassification1: yup.number().required('You must specify a sub-classification'), + subClassification2: yup.number().required('You must specify a sub-classification') + }) + ) + .isUniqueIUCNClassificationDetail('IUCN Classifications must be unique') + }) }); export interface IProjectIUCNFormProps { @@ -78,17 +73,15 @@ export interface IProjectIUCNFormProps { * @return {*} */ const ProjectIUCNForm: React.FC = (props) => { - const classes = useStyles(); - const { values, handleChange, handleSubmit, getFieldMeta, errors } = useFormikContext(); return ( ( - {values.classificationDetails.map((classificationDetail, index) => { + {values.iucn.classificationDetails.map((classificationDetail, index) => { const classificationMeta = getFieldMeta(`classificationDetails.[${index}].classification`); const subClassification1Meta = getFieldMeta(`classificationDetails.[${index}].subClassification1`); const subClassification2Meta = getFieldMeta(`classificationDetails.[${index}].subClassification2`); @@ -101,105 +94,93 @@ const ProjectIUCNForm: React.FC = (props) => { mb={1} data-testid="iucn-classification-grid" key={index}> - - - - Classification - { + classificationDetail.subClassification1 = ('' as unknown) as number; + classificationDetail.subClassification2 = ('' as unknown) as number; + handleChange(e); + }} + error={classificationMeta.touched && Boolean(classificationMeta.error)} + inputProps={{ 'aria-label': 'Classification' }}> + {props.classifications.map((item: any) => ( + + {item.label} + + ))} + + {classificationMeta.touched && classificationMeta.error} + + + + + + + + Sub-classification + - {classificationMeta.touched && classificationMeta.error} - - - - + + {subClassification1Meta.touched && subClassification1Meta.error} + + - - - - Sub-classification - - - {subClassification1Meta.touched && subClassification1Meta.error} - - - - - - - - - - - Sub-classification - - - {subClassification2Meta.touched && subClassification2Meta.error} - - - - - + + + Sub-classification + + {subClassification2Meta.touched && subClassification2Meta.error} + + + arrayHelpers.remove(index)}> @@ -209,9 +190,11 @@ const ProjectIUCNForm: React.FC = (props) => { ); })} - {errors?.classificationDetails && !Array.isArray(errors?.classificationDetails) && ( + {errors?.iucn?.classificationDetails && !Array.isArray(errors?.iucn?.classificationDetails) && ( - {errors.classificationDetails} + + {errors.iucn?.classificationDetails} + )} diff --git a/app/src/features/projects/components/ProjectLocationForm.test.tsx b/app/src/features/projects/components/ProjectLocationForm.test.tsx index 5b67ad849b..3289d35d5f 100644 --- a/app/src/features/projects/components/ProjectLocationForm.test.tsx +++ b/app/src/features/projects/components/ProjectLocationForm.test.tsx @@ -11,7 +11,7 @@ jest.spyOn(console, 'debug').mockImplementation(() => {}); describe('ProjectLocationForm', () => { it('renders correctly with default empty values', async () => { - const { asFragment } = render( + const { getByLabelText, getByText } = render( { ); await waitFor(() => { - expect(asFragment()).toMatchSnapshot(); + expect(getByText('Define Project Boundary', { exact: false })).toBeVisible(); + expect(getByLabelText('Location Description', { exact: false })).toBeVisible(); }); }); it('renders correctly with existing location values', async () => { const existingFormValues: IProjectLocationForm = { - location_description: 'a location description', - geometry: [ - { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [125.6, 10.1] - }, - properties: { - name: 'Dinagat Islands' + location: { + location_description: 'a location description', + geometry: [ + { + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [125.6, 10.1] + }, + properties: { + name: 'Dinagat Islands' + } } - } - ] + ] + } }; - const { asFragment } = render( + const { getByLabelText, getByText } = render( { ); await waitFor(() => { - expect(asFragment()).toMatchSnapshot(); - }); - }); - - it('renders correctly with errors on fields', async () => { - const existingFormValues: IProjectLocationForm = { - location_description: 'a location description', - geometry: [ - { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [125.6, 10.1] - }, - properties: { - name: 'Dinagat Islands' - } - } - ] - }; - - const { asFragment } = render( - {}}> - {() => } - - ); - - await waitFor(() => { - expect(asFragment()).toMatchSnapshot(); + expect(getByText('Define Project Boundary', { exact: false })).toBeVisible(); + expect(getByLabelText('Location Description', { exact: false })).toBeVisible(); + expect(getByText('a location description')).toBeVisible(); }); }); }); diff --git a/app/src/features/projects/components/ProjectLocationForm.tsx b/app/src/features/projects/components/ProjectLocationForm.tsx index 30d9f99e1b..2c798204be 100644 --- a/app/src/features/projects/components/ProjectLocationForm.tsx +++ b/app/src/features/projects/components/ProjectLocationForm.tsx @@ -1,4 +1,5 @@ -import Grid from '@material-ui/core/Grid'; +import Box from '@material-ui/core/Box'; +import Typography from '@material-ui/core/Typography'; import MapBoundary from 'components/boundary/MapBoundary'; import CustomTextField from 'components/fields/CustomTextField'; import { useFormikContext } from 'formik'; @@ -7,18 +8,24 @@ import React from 'react'; import yup from 'utils/YupSchema'; export interface IProjectLocationForm { - location_description: string; - geometry: Feature[]; + location: { + location_description: string; + geometry: Feature[]; + }; } export const ProjectLocationFormInitialValues: IProjectLocationForm = { - location_description: '', - geometry: [] + location: { + location_description: '', + geometry: [] + } }; export const ProjectLocationFormYupSchema = yup.object().shape({ - location_description: yup.string().max(3000, 'Cannot exceed 3000 characters'), - geometry: yup.array().min(1, 'You must specify a project boundary').required('You must specify a project boundary') + location: yup.object().shape({ + location_description: yup.string().max(3000, 'Cannot exceed 3000 characters'), + geometry: yup.array().min(1, 'A project boundary is required').required('A project boundary is required') + }) }); /** @@ -33,22 +40,23 @@ const ProjectLocationForm = () => { return ( - - - - - + + + Describe the location of this project + + - + ); }; diff --git a/app/src/features/projects/components/ProjectObjectivesForm.test.tsx b/app/src/features/projects/components/ProjectObjectivesForm.test.tsx index 1c4afff7f0..24b90bb2c7 100644 --- a/app/src/features/projects/components/ProjectObjectivesForm.test.tsx +++ b/app/src/features/projects/components/ProjectObjectivesForm.test.tsx @@ -9,7 +9,7 @@ import ProjectObjectivesForm, { describe('ProjectObjectivesForm', () => { it('renders correctly with default empty values', () => { - const { asFragment } = render( + const { getByLabelText } = render( { ); - expect(asFragment()).toMatchSnapshot(); + expect(getByLabelText('Objectives', { exact: false })).toBeVisible(); }); it('renders correctly with existing objective/caveat values', () => { const existingFormValues: IProjectObjectivesForm = { - objectives: 'a project objective', - caveats: 'a nice little caveat' + objectives: { + objectives: 'a project objective' + } }; - const { asFragment } = render( + const { getByLabelText, getByText } = render( { ); - expect(asFragment()).toMatchSnapshot(); + expect(getByLabelText('Objectives', { exact: false })).toBeVisible(); + expect(getByText('a project objective')).toBeVisible(); }); }); diff --git a/app/src/features/projects/components/ProjectObjectivesForm.tsx b/app/src/features/projects/components/ProjectObjectivesForm.tsx index 2ccb80a88f..eb3ca3c283 100644 --- a/app/src/features/projects/components/ProjectObjectivesForm.tsx +++ b/app/src/features/projects/components/ProjectObjectivesForm.tsx @@ -5,21 +5,21 @@ import React from 'react'; import yup from 'utils/YupSchema'; export interface IProjectObjectivesForm { - objectives: string; - caveats: string; + objectives: { + objectives: string; + }; } export const ProjectObjectivesFormInitialValues: IProjectObjectivesForm = { - objectives: '', - caveats: '' + objectives: { + objectives: '' + } }; export const ProjectObjectivesFormYupSchema = yup.object().shape({ - objectives: yup - .string() - .max(3000, 'Cannot exceed 3000 characters') - .required('You must provide objectives for the project'), - caveats: yup.string().max(3000, 'Cannot exceed 3000 characters') + objectives: yup.object().shape({ + objectives: yup.string().max(3000, 'Cannot exceed 3000 characters').required('Objectives are required') + }) }); /** @@ -36,10 +36,11 @@ const ProjectObjectivesForm = () => {
- - - - +
diff --git a/app/src/features/projects/components/ProjectPartnershipsForm.test.tsx b/app/src/features/projects/components/ProjectPartnershipsForm.test.tsx index c7ffb9f846..2427336c12 100644 --- a/app/src/features/projects/components/ProjectPartnershipsForm.test.tsx +++ b/app/src/features/projects/components/ProjectPartnershipsForm.test.tsx @@ -32,7 +32,7 @@ const stakeholder_partnerships: IMultiAutocompleteFieldOption[] = [ describe('ProjectPartnershipsForm', () => { it('renders correctly with default empty values', () => { - const { asFragment } = render( + const { getByLabelText } = render( { ); - expect(asFragment()).toMatchSnapshot(); + expect(getByLabelText('Indigenous Partnerships', { exact: false })).toBeVisible(); + expect(getByLabelText('Other Partnerships', { exact: false })).toBeVisible(); }); it('renders correctly with existing funding values', () => { const existingFormValues: IProjectPartnershipsForm = { - indigenous_partnerships: [1, 2], - stakeholder_partnerships: ['partner 1'] + partnerships: { + indigenous_partnerships: [1, 2], + stakeholder_partnerships: [(1 as unknown) as string] + } }; - const { asFragment } = render( + const { getByLabelText, getByText } = render( { ); - expect(asFragment()).toMatchSnapshot(); + expect(getByLabelText('Indigenous Partnerships', { exact: false })).toBeVisible(); + expect(getByLabelText('Other Partnerships', { exact: false })).toBeVisible(); + expect(getByText('nation 1')).toBeVisible(); + expect(getByText('nation 2')).toBeVisible(); + expect(getByText('partner 1', { exact: false })).toBeVisible(); }); }); diff --git a/app/src/features/projects/components/ProjectPartnershipsForm.tsx b/app/src/features/projects/components/ProjectPartnershipsForm.tsx index 7e2a7bd90a..6f2a10defa 100644 --- a/app/src/features/projects/components/ProjectPartnershipsForm.tsx +++ b/app/src/features/projects/components/ProjectPartnershipsForm.tsx @@ -7,13 +7,17 @@ import React from 'react'; import yup from 'utils/YupSchema'; export interface IProjectPartnershipsForm { - indigenous_partnerships: number[]; - stakeholder_partnerships: string[]; + partnerships: { + indigenous_partnerships: number[]; + stakeholder_partnerships: string[]; + }; } export const ProjectPartnershipsFormInitialValues: IProjectPartnershipsForm = { - indigenous_partnerships: [], - stakeholder_partnerships: [] + partnerships: { + indigenous_partnerships: [], + stakeholder_partnerships: [] + } }; export const ProjectPartnershipsFormYupSchema = yup.object().shape({}); @@ -38,7 +42,7 @@ const ProjectPartnershipsForm: React.FC = (props) = (props) = (props) => { - const { values, handleChange, handleSubmit, getFieldMeta, errors } = useFormikContext(); - - return ( -
- ( - - - {values.permits?.map((permit, index) => { - const permitNumberMeta = getFieldMeta(`permits.[${index}].permit_number`); - const permitTypeMeta = getFieldMeta(`permits.[${index}].permit_type`); - - return ( - - - - - - - - Permit Type - - {permitTypeMeta.touched && permitTypeMeta.error} - - - - arrayHelpers.remove(index)}> - - - - - - ); - })} - - {errors?.permits && !Array.isArray(errors?.permits) && ( - - {errors.permits} - - )} - - - - - )} - /> - - ); -}; - -export default ProjectPermitForm; diff --git a/app/src/features/projects/components/__snapshots__/ProjectCoordinatorForm.test.tsx.snap b/app/src/features/projects/components/__snapshots__/ProjectCoordinatorForm.test.tsx.snap deleted file mode 100644 index 38f5817a24..0000000000 --- a/app/src/features/projects/components/__snapshots__/ProjectCoordinatorForm.test.tsx.snap +++ /dev/null @@ -1,755 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Project Contact Form renders correctly the empty component correctly 1`] = ` - -
-
-
-
- -
- - -
-
-
-
-
- -
- - -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- - Share Contact Details - -

- Do you want the project contact’s name and email address visible to the public? -

-
-
- - -

-

-
-
-
-
-
-`; - -exports[`Project Contact Form renders correctly the filled component correctly 1`] = ` - -
-
-
-
- -
- - -
-
-
-
-
- -
- - -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- - Share Contact Details - -

- Do you want the project contact’s name and email address visible to the public? -

-
-
- - -

-

-
-
-
-
-
-`; diff --git a/app/src/features/projects/components/__snapshots__/ProjectDetailsForm.test.tsx.snap b/app/src/features/projects/components/__snapshots__/ProjectDetailsForm.test.tsx.snap deleted file mode 100644 index 67baab312b..0000000000 --- a/app/src/features/projects/components/__snapshots__/ProjectDetailsForm.test.tsx.snap +++ /dev/null @@ -1,709 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ProjectDetailsForm renders correctly with default empty values 1`] = ` - -
-
-
-
- -
- - -
-
-
-
-
- -
-
- - ​ - -
- - - -
-

-

-
-
- -
-
-
-
- -
- - -
-
-
-
-
- -
- - -
-
-
-
-
-
-
-`; - -exports[`ProjectDetailsForm renders correctly with existing details values 1`] = ` - -
-
-
-
- -
- - -
-
-
-
-
- -
-
- type 2 -
- - - -
-

-

-
-
- -
-
-
-
- -
- - -
-
-
-
-
- -
- - -
-
-
-
-
-
-
-`; diff --git a/app/src/features/projects/components/__snapshots__/ProjectDraftForm.test.tsx.snap b/app/src/features/projects/components/__snapshots__/ProjectDraftForm.test.tsx.snap deleted file mode 100644 index e2524fdd0f..0000000000 --- a/app/src/features/projects/components/__snapshots__/ProjectDraftForm.test.tsx.snap +++ /dev/null @@ -1,161 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Project Draft Form renders correctly with empty initial values 1`] = ` - -
-
- -
- - -
-
-
-
-`; - -exports[`Project Draft Form renders correctly with errors 1`] = ` - -
-
- -
- - -
-

- Error this is a required field -

-
-
-
-`; - -exports[`Project Draft Form renders correctly with populated initial values 1`] = ` - -
-
- -
- - -
-
-
-
-`; diff --git a/app/src/features/projects/components/__snapshots__/ProjectFundingForm.test.tsx.snap b/app/src/features/projects/components/__snapshots__/ProjectFundingForm.test.tsx.snap deleted file mode 100644 index 87ed2b8cc0..0000000000 --- a/app/src/features/projects/components/__snapshots__/ProjectFundingForm.test.tsx.snap +++ /dev/null @@ -1,277 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ProjectFundingForm renders correctly with default empty values 1`] = ` - -
-
-
-
-

- Funding Sources ( - 0 - ) -

- -
-
-
-
    -
    -
    -
    - No Funding Sources -
    -
    -
    -
-
-
-
-
-
- -`; - -exports[`ProjectFundingForm renders correctly with existing funding values 1`] = ` - -
-
-
-
-

- Funding Sources ( - 1 - ) -

- -
-
-
-
    -
  • -
    -
    -

    - Funding source code - - ( - Investment action category - ) - -

    - - -
    -
    -
    -
    -
    -

    - Agency Project ID -

    -

    - 111 -

    -
    -
    -

    - Funding Amount -

    -

    - $222 -

    -
    -
    -

    - Start / End Date -

    -

    - Mar 14, 2021 - Apr 14, 2021 -

    -
    -
    -
    -
    -
  • -
-
-
-
-
-
- -`; diff --git a/app/src/features/projects/components/__snapshots__/ProjectIUCNForm.test.tsx.snap b/app/src/features/projects/components/__snapshots__/ProjectIUCNForm.test.tsx.snap deleted file mode 100644 index 0aa6e371ed..0000000000 --- a/app/src/features/projects/components/__snapshots__/ProjectIUCNForm.test.tsx.snap +++ /dev/null @@ -1,637 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ProjectIUCNForm changes fields on the IUCN menu items as expected 1`] = ` - -
-
-
-
-
-
- -
-
- Class 1 -
- - - -
-

-

-
-
- - - -
-
-
- -
-
- A Sub-class 1 -
- - - -
-

-

-
-
- - - -
-
-
- -
-
- A Sub-class 2 -
- - - -
-

-

-
-
-
- -
-
-
- -
-
-
-
-`; - -exports[`ProjectIUCNForm renders correctly with default empty values 1`] = ` - -
-
-
- -
-
-
-
-`; - -exports[`ProjectIUCNForm renders correctly with existing details values 1`] = ` - -
-
-
-
-
-
- -
-
- Class 1 -
- - - -
-

-

-
-
- - - -
-
-
- -
-
- A Sub-class 1 -
- - - -
-

-

-
-
- - - -
-
-
- -
-
- A Sub-class 2 -
- - - -
-

-

-
-
-
- -
-
-
- -
-
-
-
-`; diff --git a/app/src/features/projects/components/__snapshots__/ProjectLocationForm.test.tsx.snap b/app/src/features/projects/components/__snapshots__/ProjectLocationForm.test.tsx.snap deleted file mode 100644 index e0cd113762..0000000000 --- a/app/src/features/projects/components/__snapshots__/ProjectLocationForm.test.tsx.snap +++ /dev/null @@ -1,1324 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ProjectLocationForm renders correctly with default empty values 1`] = ` - -
-
-
-
- -
- - -
-

- error is here -

-
-
-
-

- Project Boundary * -

-
-

- Define your boundary by selecting a boundary from an existing layer or by uploading KML file or shapefile. -

-
-

- To select a boundary from an existing layer, select a layer from the dropdown, click a boundary on the map and click 'Add Boundary'. -

-
-
-
- -
-
- -
-
- - ​ - -
- - - -
-
-
-
-
-
-
- - - -`; - -exports[`ProjectLocationForm renders correctly with existing location values 1`] = ` - -
-
-
-
- -
- - -
-
-
-
-

- Project Boundary * -

-
-

- Define your boundary by selecting a boundary from an existing layer or by uploading KML file or shapefile. -

-
-

- To select a boundary from an existing layer, select a layer from the dropdown, click a boundary on the map and click 'Add Boundary'. -

-
-
-
- -
-
- -
-
- - ​ - -
- - - -
-
-
-
-
-
-
- - - -`; diff --git a/app/src/features/projects/components/__snapshots__/ProjectObjectivesForm.test.tsx.snap b/app/src/features/projects/components/__snapshots__/ProjectObjectivesForm.test.tsx.snap deleted file mode 100644 index f6dc2187ed..0000000000 --- a/app/src/features/projects/components/__snapshots__/ProjectObjectivesForm.test.tsx.snap +++ /dev/null @@ -1,201 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ProjectObjectivesForm renders correctly with default empty values 1`] = ` - -
-
-
-
- -
- - -
-
-
-
-
- -
- - -
-
-
-
-
-
-`; diff --git a/app/src/features/projects/components/__snapshots__/ProjectPartnershipsForm.test.tsx.snap b/app/src/features/projects/components/__snapshots__/ProjectPartnershipsForm.test.tsx.snap deleted file mode 100644 index 47e6be2d7e..0000000000 --- a/app/src/features/projects/components/__snapshots__/ProjectPartnershipsForm.test.tsx.snap +++ /dev/null @@ -1,487 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ProjectPartnershipsForm renders correctly with default empty values 1`] = ` - -
-
-
- -
-
- -
-
-
-
-`; - -exports[`ProjectPartnershipsForm renders correctly with existing funding values 1`] = ` - -
-
-
- -
-
- -
-
-
-
-`; diff --git a/app/src/features/projects/components/__snapshots__/ProjectPermitForm.test.tsx.snap b/app/src/features/projects/components/__snapshots__/ProjectPermitForm.test.tsx.snap deleted file mode 100644 index 82c54de28d..0000000000 --- a/app/src/features/projects/components/__snapshots__/ProjectPermitForm.test.tsx.snap +++ /dev/null @@ -1,930 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ProjectPermitForm renders correctly with default empty values 1`] = ` - -
-
-
-
- -
-
- - -`; - -exports[`ProjectPermitForm renders correctly with error on the permits field due to duplicates 1`] = ` - -
-
-
-
-
-
-
- -
- - -
-
-
-
-
- -
-
- Park Use Permit -
- - - -
-

-

-
-
- -
-
-
-
-
-
-
- -
- - -
-
-
-
-
- -
-
- Scientific Fish Collection Permit -
- - - -
-

-

-
-
- -
-
-
-
-
-

- Error is here -

-
-
- -
-
-
-
-`; - -exports[`ProjectPermitForm renders correctly with errors on the permit_number and permit_type fields 1`] = ` - -
-
-
-
-
-
-
- -
- - -
-

- Error here -

-
-
-
-
- -
-
- Scientific Fish Collection Permit -
- - - -
-

- Error here as well -

-
-
-
- -
-
-
-
-
- -
-
-
-
-`; - -exports[`ProjectPermitForm renders correctly with existing permit values 1`] = ` - -
-
-
-
-
-
-
- -
- - -
-
-
-
-
- -
-
- Park Use Permit -
- - - -
-

-

-
-
- -
-
-
-
-
-
-
- -
- - -
-
-
-
-
- -
-
- Scientific Fish Collection Permit -
- - - -
-

-

-
-
- -
-
-
-
-
- -
-
-
-
-`; diff --git a/app/src/features/projects/create/CreateProjectForm.tsx b/app/src/features/projects/create/CreateProjectForm.tsx new file mode 100644 index 0000000000..13c046ddcd --- /dev/null +++ b/app/src/features/projects/create/CreateProjectForm.tsx @@ -0,0 +1,272 @@ +import { Box, Button, Divider, Typography } from '@material-ui/core'; +import { Theme } from '@material-ui/core/styles/createMuiTheme'; +import makeStyles from '@material-ui/core/styles/makeStyles'; +import HorizontalSplitFormComponent from 'components/fields/HorizontalSplitFormComponent'; +import { ScrollToFormikError } from 'components/formik/ScrollToFormikError'; +import { Formik, FormikProps } from 'formik'; +import { IGetAllCodeSetsResponse } from 'interfaces/useCodesApi.interface'; +import { ICreateProjectRequest } from 'interfaces/useProjectApi.interface'; +import React from 'react'; +import ProjectCoordinatorForm, { + ProjectCoordinatorInitialValues, + ProjectCoordinatorYupSchema +} from '../components/ProjectCoordinatorForm'; +import ProjectDetailsForm, { + ProjectDetailsFormInitialValues, + ProjectDetailsFormYupSchema +} from '../components/ProjectDetailsForm'; +import ProjectFundingForm, { + ProjectFundingFormInitialValues, + ProjectFundingFormYupSchema +} from '../components/ProjectFundingForm'; +import ProjectIUCNForm, { ProjectIUCNFormInitialValues, ProjectIUCNFormYupSchema } from '../components/ProjectIUCNForm'; +import ProjectLocationForm, { + ProjectLocationFormInitialValues, + ProjectLocationFormYupSchema +} from '../components/ProjectLocationForm'; +import ProjectObjectivesForm, { + ProjectObjectivesFormInitialValues, + ProjectObjectivesFormYupSchema +} from '../components/ProjectObjectivesForm'; +import ProjectPartnershipsForm, { + ProjectPartnershipsFormInitialValues, + ProjectPartnershipsFormYupSchema +} from '../components/ProjectPartnershipsForm'; + +const useStyles = makeStyles((theme: Theme) => ({ + actionButton: { + minWidth: '6rem', + '& + button': { + marginLeft: '0.5rem' + } + }, + sectionDivider: { + height: '1px', + marginTop: theme.spacing(5), + marginBottom: theme.spacing(5) + }, + breadCrumbLink: { + display: 'flex', + alignItems: 'center', + cursor: 'pointer' + }, + breadCrumbLinkIcon: { + marginRight: '0.25rem' + } +})); + +export interface ICreateProjectForm { + codes: IGetAllCodeSetsResponse; + handleSubmit: (formikData: ICreateProjectRequest) => void; + handleCancel: () => void; + handleDraft: (value: React.SetStateAction) => void; + formikRef: React.RefObject>; +} + +export const initialProjectFieldData: ICreateProjectRequest = { + ...ProjectDetailsFormInitialValues, + ...ProjectObjectivesFormInitialValues, + ...ProjectCoordinatorInitialValues, + ...ProjectLocationFormInitialValues, + ...ProjectIUCNFormInitialValues, + ...ProjectFundingFormInitialValues, + ...ProjectPartnershipsFormInitialValues +}; + +export const validationProjectYupSchema = ProjectCoordinatorYupSchema.concat(ProjectDetailsFormYupSchema) + .concat(ProjectObjectivesFormYupSchema) + .concat(ProjectLocationFormYupSchema) + .concat(ProjectIUCNFormYupSchema) + .concat(ProjectFundingFormYupSchema) + .concat(ProjectPartnershipsFormYupSchema); + +/** + * Form for creating a new project. + * + * @return {*} + */ +const CreateProjectForm: React.FC = (props) => { + const { codes, formikRef } = props; + + const classes = useStyles(); + + const handleSubmit = async (formikData: ICreateProjectRequest) => { + props.handleSubmit(formikData); + }; + + const handleCancel = () => { + props.handleCancel(); + }; + + const handleDraft = () => { + props.handleDraft(true); + }; + + return ( + + + <> + + + + { + return { value: item.id, label: item.name }; + }) || [] + } + activity={ + codes?.activity?.map((item) => { + return { value: item.id, label: item.name }; + }) || [] + } + /> + + + + + + IUCN Conservation Actions Classification + + + Conservation actions are specific actions or sets of tasks undertaken by project staff designed to + reach each of the project's objectives. + + + + { + return { value: item.id, label: item.name }; + }) || [] + } + subClassifications1={ + codes?.iucn_conservation_action_level_2_subclassification?.map((item) => { + return { value: item.id, iucn1_id: item.iucn1_id, label: item.name }; + }) || [] + } + subClassifications2={ + codes?.iucn_conservation_action_level_3_subclassification?.map((item) => { + return { value: item.id, iucn2_id: item.iucn2_id, label: item.name }; + }) || [] + } + /> + + + + }> + + + + { + return item.name; + }) || [] + } + /> + }> + + + + + + + Funding Sources + + + Specify funding sources for the project. Note: Dollar amounts are not intended to + be exact, please round to the nearest 100. + + + { + return { value: item.id, label: item.name }; + }) || [] + } + investment_action_category={ + codes?.investment_action_category?.map((item) => { + return { value: item.id, fs_id: item.fs_id, label: item.name }; + }) || [] + } + /> + + + + + Partnerships + + + Additional partnerships that have not been previously identified as a funding sources. + + + { + return { value: item.id, label: item.name }; + }) || [] + } + stakeholder_partnerships={ + codes?.funding_source?.map((item) => { + return { value: item.name, label: item.name }; + }) || [] + } + /> + + + + }> + + + + }> + + + + + + + + + + + + ); +}; + +export default CreateProjectForm; diff --git a/app/src/features/projects/create/CreateProjectPage.test.tsx b/app/src/features/projects/create/CreateProjectPage.test.tsx index c6e513d3cc..5eeed97dd1 100644 --- a/app/src/features/projects/create/CreateProjectPage.test.tsx +++ b/app/src/features/projects/create/CreateProjectPage.test.tsx @@ -4,7 +4,6 @@ import { fireEvent, getByText as rawGetByText, render, - screen, waitFor } from '@testing-library/react'; import { DialogContextProvider } from 'contexts/dialogContext'; @@ -14,10 +13,10 @@ import { ProjectIUCNFormInitialValues } from 'features/projects/components/Proje import { ProjectLocationFormInitialValues } from 'features/projects/components/ProjectLocationForm'; import { ProjectObjectivesFormInitialValues } from 'features/projects/components/ProjectObjectivesForm'; import { ProjectPartnershipsFormInitialValues } from 'features/projects/components/ProjectPartnershipsForm'; -import { ProjectPermitFormInitialValues } from 'features/projects/components/ProjectPermitForm'; import CreateProjectPage from 'features/projects/create/CreateProjectPage'; import { createMemoryHistory } from 'history'; import { useBiohubApi } from 'hooks/useBioHubApi'; +import { IGetAllCodeSetsResponse } from 'interfaces/useCodesApi.interface'; import React from 'react'; import { MemoryRouter, Router } from 'react-router'; @@ -26,15 +25,12 @@ const history = createMemoryHistory(); jest.mock('../../../hooks/useBioHubApi'); const mockUseBiohubApi = { codes: { - getAllCodeSets: jest.fn, []>() + getAllCodeSets: jest.fn, []>() }, draft: { createDraft: jest.fn, []>(), updateDraft: jest.fn, []>(), getDraft: jest.fn() - }, - permit: { - getNonSamplingPermits: jest.fn, []>() } }; @@ -59,7 +55,6 @@ describe('CreateProjectPage', () => { mockBiohubApi().draft.createDraft.mockClear(); mockBiohubApi().draft.updateDraft.mockClear(); mockBiohubApi().draft.getDraft.mockClear(); - mockBiohubApi().permit.getNonSamplingPermits.mockClear(); jest.spyOn(console, 'debug').mockImplementation(() => {}); }); @@ -69,39 +64,29 @@ describe('CreateProjectPage', () => { }); it('renders the initial default page correctly', async () => { - mockBiohubApi().codes.getAllCodeSets.mockResolvedValue({ + mockBiohubApi().codes.getAllCodeSets.mockResolvedValue(({ coordinator_agency: [{ id: 1, name: 'A Rocha Canada' }] - }); - mockBiohubApi().permit.getNonSamplingPermits.mockResolvedValue([{ permit_id: 1, number: 1, type: 'Wildlife' }]); + } as unknown) as IGetAllCodeSetsResponse); - const { getByText, getAllByText, asFragment } = renderContainer(); + const { getByText } = renderContainer(); await waitFor(() => { - expect(getAllByText('Project Contact').length).toEqual(2); - - expect(getByText('Project Permits')).toBeVisible(); + expect(getByText('Create Project')).toBeVisible(); expect(getByText('General Information')).toBeVisible(); - expect(getByText('Objectives')).toBeVisible(); - - expect(getByText('Locations')).toBeVisible(); - - expect(getByText('IUCN Conservation Actions Classification')).toBeVisible(); + expect(getByText('Project Coordinator')).toBeVisible(); - expect(getByText('Funding')).toBeVisible(); + expect(getByText('Funding and Partnerships')).toBeVisible(); - expect(getByText('Partnerships')).toBeVisible(); - - expect(asFragment()).toMatchSnapshot(); + expect(getByText('Location and Boundary')).toBeVisible(); }); }); it('shows the page title', async () => { - mockBiohubApi().codes.getAllCodeSets.mockResolvedValue({ + mockBiohubApi().codes.getAllCodeSets.mockResolvedValue(({ coordinator_agency: [{ id: 1, name: 'A Rocha Canada' }] - }); - mockBiohubApi().permit.getNonSamplingPermits.mockResolvedValue([{ permit_id: 1, number: 1, type: 'Wildlife' }]); + } as unknown) as IGetAllCodeSetsResponse); const { findByText } = renderContainer(); const PageTitle = await findByText('Create Project'); @@ -109,50 +94,21 @@ describe('CreateProjectPage', () => { expect(PageTitle).toBeVisible(); }); - it('navigates to a different section on click of that section label', async () => { - mockBiohubApi().codes.getAllCodeSets.mockResolvedValue({ - coordinator_agency: [{ id: 1, name: 'A Rocha Canada' }] - }); - mockBiohubApi().permit.getNonSamplingPermits.mockResolvedValue([{ permit_id: 1, number: 1, type: 'Wildlife' }]); - - const { getByText, getAllByText, queryByLabelText } = renderContainer(); - - // wait for initial page to load - await waitFor(() => { - expect(getAllByText('Project Contact').length).toEqual(2); - - expect(getByText('Project Permits')).toBeVisible(); - - expect(getByText('General Information')).toBeVisible(); - - expect(queryByLabelText('Project Type')).toBeNull(); - }); - - fireEvent.click(getByText('General Information')); - - await waitFor(() => { - expect(getAllByText('General Information').length).toEqual(2); - - expect(queryByLabelText('Project Type')).toBeVisible(); - }); - }); - describe('Are you sure? Dialog', () => { it('shows warning dialog if the user clicks the `Cancel and Exit` button', async () => { - mockBiohubApi().codes.getAllCodeSets.mockResolvedValue({ + mockBiohubApi().codes.getAllCodeSets.mockResolvedValue(({ coordinator_agency: [{ id: 1, name: 'A Rocha Canada' }] - }); - mockBiohubApi().permit.getNonSamplingPermits.mockResolvedValue([{ permit_id: 1, number: 1, type: 'Wildlife' }]); + } as unknown) as IGetAllCodeSetsResponse); history.push('/home'); history.push('/admin/projects/create'); - const { findByText, getByRole } = renderContainer(); - const BackToProjectsButton = await findByText('Cancel and Exit', { exact: false }); + const { findByText, getByRole, findAllByText } = renderContainer(); + const BackToProjectsButton = await findAllByText('Cancel'); - fireEvent.click(BackToProjectsButton); - const AreYouSureTitle = await findByText('Cancel Create Project'); - const AreYouSureText = await findByText('Are you sure you want to cancel?'); + fireEvent.click(BackToProjectsButton[0]); + const AreYouSureTitle = await findByText('Cancel Project Creation'); + const AreYouSureText = await findByText('Are you sure you want to cancel?', { exact: false }); const AreYouSureYesButton = await rawFindByText(getByRole('dialog'), 'Yes', { exact: false }); expect(AreYouSureTitle).toBeVisible(); @@ -161,18 +117,17 @@ describe('CreateProjectPage', () => { }); it('calls history.push() if the user clicks `Yes`', async () => { - mockBiohubApi().codes.getAllCodeSets.mockResolvedValue({ + mockBiohubApi().codes.getAllCodeSets.mockResolvedValue(({ coordinator_agency: [{ id: 1, name: 'A Rocha Canada' }] - }); - mockBiohubApi().permit.getNonSamplingPermits.mockResolvedValue([{ permit_id: 1, number: 1, type: 'Wildlife' }]); + } as unknown) as IGetAllCodeSetsResponse); history.push('/home'); history.push('/admin/projects/create'); - const { findByText, getByRole } = renderContainer(); - const BackToProjectsButton = await findByText('Cancel and Exit', { exact: false }); + const { findAllByText, getByRole } = renderContainer(); + const BackToProjectsButton = await findAllByText('Cancel'); - fireEvent.click(BackToProjectsButton); + fireEvent.click(BackToProjectsButton[0]); const AreYouSureYesButton = await rawFindByText(getByRole('dialog'), 'Yes', { exact: false }); expect(history.location.pathname).toEqual('/admin/projects/create'); @@ -181,19 +136,18 @@ describe('CreateProjectPage', () => { }); it('does nothing if the user clicks `No`', async () => { - mockBiohubApi().codes.getAllCodeSets.mockResolvedValue({ + mockBiohubApi().codes.getAllCodeSets.mockResolvedValue(({ coordinator_agency: [{ id: 1, name: 'A Rocha Canada' }] - }); - mockBiohubApi().permit.getNonSamplingPermits.mockResolvedValue([{ permit_id: 1, number: 1, type: 'Wildlife' }]); + } as unknown) as IGetAllCodeSetsResponse); history.push('/home'); history.push('/admin/projects/create'); - const { findByText, getByRole } = renderContainer(); - const BackToProjectsButton = await findByText('Cancel and Exit', { exact: false }); + const { findAllByText, getByRole } = renderContainer(); + const BackToProjectsButton = await findAllByText('Cancel'); - fireEvent.click(BackToProjectsButton); - const AreYouSureNoButton = await rawFindByText(getByRole('dialog'), 'No', { exact: false }); + fireEvent.click(BackToProjectsButton[0]); + const AreYouSureNoButton = await rawFindByText(getByRole('dialog'), 'No'); expect(history.location.pathname).toEqual('/admin/projects/create'); fireEvent.click(AreYouSureNoButton); @@ -202,14 +156,15 @@ describe('CreateProjectPage', () => { }); describe('draft project', () => { - beforeEach(() => { - mockBiohubApi().codes.getAllCodeSets.mockResolvedValue({ - coordinator_agency: [{ id: 1, name: 'A Rocha Canada' }] - }); - mockBiohubApi().permit.getNonSamplingPermits.mockResolvedValue([{ permit_id: 1, number: 1, type: 'Wildlife' }]); + afterEach(() => { + jest.restoreAllMocks(); }); it('preloads draft data and populates on form fields', async () => { + mockBiohubApi().codes.getAllCodeSets.mockResolvedValue(({ + coordinator_agency: [{ id: 1, name: 'A Rocha Canada' }] + } as unknown) as IGetAllCodeSetsResponse); + mockBiohubApi().draft.getDraft.mockResolvedValue({ id: 1, name: 'My draft', @@ -221,50 +176,49 @@ describe('CreateProjectPage', () => { coordinator_agency: '', share_contact_details: 'false' }, - permit: ProjectPermitFormInitialValues, - project: ProjectDetailsFormInitialValues, - objectives: ProjectObjectivesFormInitialValues, - location: ProjectLocationFormInitialValues, - iucn: ProjectIUCNFormInitialValues, - funding: ProjectFundingFormInitialValues, - partnerships: ProjectPartnershipsFormInitialValues + project: ProjectDetailsFormInitialValues.project, + objectives: ProjectObjectivesFormInitialValues.objectives, + location: ProjectLocationFormInitialValues.location, + iucn: ProjectIUCNFormInitialValues.iucn, + funding: ProjectFundingFormInitialValues.funding, + partnerships: ProjectPartnershipsFormInitialValues.partnerships } }); - render( + const { getByDisplayValue } = render( ); await waitFor(() => { - expect(screen.getByDisplayValue('Draft first name')).toBeInTheDocument(); - expect(screen.getByDisplayValue('Draft last name')).toBeInTheDocument(); - expect(screen.getByDisplayValue('draftemail@example.com')).toBeInTheDocument(); + expect(getByDisplayValue('Draft first name', { exact: false })).toBeInTheDocument(); + expect(getByDisplayValue('Draft last name', { exact: false })).toBeInTheDocument(); + expect(getByDisplayValue('draftemail@example.com', { exact: false })).toBeInTheDocument(); }); }); it('opens the save as draft and exit dialog', async () => { - const { getByText, findByText } = renderContainer(); + const { getByLabelText, findAllByText } = renderContainer(); - const saveAsDraftButton = await findByText('Save as Draft and Exit'); + const saveAsDraftButton = await findAllByText('Save Draft'); - fireEvent.click(saveAsDraftButton); + fireEvent.click(saveAsDraftButton[0]); await waitFor(() => { - expect(getByText('Save Incomplete Project as a Draft')).toBeVisible(); + expect(getByLabelText('Draft Name *')).toBeVisible(); }); }); it('closes the dialog on cancel button click', async () => { - const { getByText, findByText, queryByText, getByRole } = renderContainer(); + const { getByLabelText, findAllByText, getByRole, queryByLabelText } = renderContainer(); - const saveAsDraftButton = await findByText('Save as Draft and Exit'); + const saveAsDraftButton = await findAllByText('Save Draft'); - fireEvent.click(saveAsDraftButton); + fireEvent.click(saveAsDraftButton[1]); await waitFor(() => { - expect(getByText('Save Incomplete Project as a Draft')).toBeVisible(); + expect(getByLabelText('Draft Name *')).toBeVisible(); }); const cancelButton = rawGetByText(getByRole('dialog'), 'Cancel'); @@ -272,7 +226,7 @@ describe('CreateProjectPage', () => { fireEvent.click(cancelButton); await waitFor(() => { - expect(queryByText('Save Incomplete Project as a Draft')).not.toBeInTheDocument(); + expect(queryByLabelText('Draft Name *')).not.toBeInTheDocument(); }); }); @@ -282,14 +236,14 @@ describe('CreateProjectPage', () => { date: '2021-01-20' }); - const { getByText, findByText, queryByText, getByLabelText } = renderContainer(); + const { getByText, findAllByText, queryByLabelText, getByLabelText } = renderContainer(); - const saveAsDraftButton = await findByText('Save as Draft and Exit'); + const saveAsDraftButton = await findAllByText('Save Draft'); - fireEvent.click(saveAsDraftButton); + fireEvent.click(saveAsDraftButton[0]); await waitFor(() => { - expect(getByText('Save Incomplete Project as a Draft')).toBeVisible(); + expect(getByLabelText('Draft Name *')).toBeVisible(); }); fireEvent.change(getByLabelText('Draft Name *'), { target: { value: 'draft name' } }); @@ -299,13 +253,13 @@ describe('CreateProjectPage', () => { await waitFor(() => { expect(mockBiohubApi().draft.createDraft).toHaveBeenCalledWith('draft name', expect.any(Object)); - expect(queryByText('Save Incomplete Project as a Draft')).not.toBeInTheDocument(); + expect(queryByLabelText('Draft Name *')).not.toBeInTheDocument(); }); - fireEvent.click(getByText('Save as Draft and Exit')); + fireEvent.click(saveAsDraftButton[0]); await waitFor(() => { - expect(getByText('Save Incomplete Project as a Draft')).toBeVisible(); + expect(getByLabelText('Draft Name *')).toBeVisible(); }); fireEvent.change(getByLabelText('Draft Name *'), { target: { value: 'draft name' } }); @@ -315,7 +269,7 @@ describe('CreateProjectPage', () => { await waitFor(() => { expect(mockBiohubApi().draft.updateDraft).toHaveBeenCalledWith(1, 'draft name', expect.any(Object)); - expect(queryByText('Save Incomplete Project as a Draft')).not.toBeInTheDocument(); + expect(queryByLabelText('Draft Name *')).not.toBeInTheDocument(); }); }); @@ -325,22 +279,22 @@ describe('CreateProjectPage', () => { date: '2021-01-20' }); - const { getByText, getAllByText, findByText, queryByText, getByLabelText } = renderContainer(); + const { getByText, findAllByText, getByLabelText, queryByLabelText } = renderContainer(); // wait for initial page to load await waitFor(() => { - expect(getAllByText('Project Contact').length).toEqual(2); + expect(getByText('General Information')).toBeVisible(); }); // update first name field fireEvent.change(getByLabelText('First Name *'), { target: { value: 'draft first name' } }); - const saveAsDraftButton = await findByText('Save as Draft and Exit'); + const saveAsDraftButton = await findAllByText('Save Draft'); - fireEvent.click(saveAsDraftButton); + fireEvent.click(saveAsDraftButton[0]); await waitFor(() => { - expect(getByText('Save Incomplete Project as a Draft')).toBeVisible(); + expect(getByLabelText('Draft Name *')).toBeVisible(); }); fireEvent.change(getByLabelText('Draft Name *'), { target: { value: 'draft name' } }); @@ -356,25 +310,30 @@ describe('CreateProjectPage', () => { coordinator_agency: '', share_contact_details: 'false' }, - permit: expect.any(Object), - project: expect.any(Object), - objectives: expect.any(Object), - location: expect.any(Object), - iucn: expect.any(Object), - funding: expect.any(Object), - partnerships: expect.any(Object) + project: { + project_name: '', + project_type: ('' as unknown) as number, + project_activities: [], + start_date: '', + end_date: '' + }, + objectives: { objectives: '' }, + location: { location_description: '', geometry: [] }, + iucn: { classificationDetails: [] }, + funding: { funding_sources: [] }, + partnerships: { indigenous_partnerships: [], stakeholder_partnerships: [] } }); - expect(queryByText('Save Incomplete Project as a Draft')).not.toBeInTheDocument(); + expect(queryByLabelText('Draft Name *')).not.toBeInTheDocument(); }); // update last name field fireEvent.change(getByLabelText('Last Name *'), { target: { value: 'draft last name' } }); - fireEvent.click(getByText('Save as Draft and Exit')); + fireEvent.click(saveAsDraftButton[0]); await waitFor(() => { - expect(getByText('Save Incomplete Project as a Draft')).toBeVisible(); + expect(getByLabelText('Draft Name *')).toBeVisible(); }); fireEvent.change(getByLabelText('Draft Name *'), { target: { value: 'draft name' } }); @@ -390,16 +349,21 @@ describe('CreateProjectPage', () => { coordinator_agency: '', share_contact_details: 'false' }, - permit: expect.any(Object), - project: expect.any(Object), - objectives: expect.any(Object), - location: expect.any(Object), - iucn: expect.any(Object), - funding: expect.any(Object), - partnerships: expect.any(Object) + project: { + project_name: '', + project_type: ('' as unknown) as number, + project_activities: [], + start_date: '', + end_date: '' + }, + objectives: { objectives: '' }, + location: { location_description: '', geometry: [] }, + iucn: { classificationDetails: [] }, + funding: { funding_sources: [] }, + partnerships: { indigenous_partnerships: [], stakeholder_partnerships: [] } }); - expect(queryByText('Save Incomplete Project as a Draft')).not.toBeInTheDocument(); + expect(queryByLabelText('Draft Name *')).not.toBeInTheDocument(); }); }); @@ -408,14 +372,14 @@ describe('CreateProjectPage', () => { throw new Error('Draft failed exception!'); }); - const { getByText, findByText, queryByText, getByLabelText } = renderContainer(); + const { getByText, findAllByText, getByLabelText, queryByLabelText } = renderContainer(); - const saveAsDraftButton = await findByText('Save as Draft and Exit'); + const saveAsDraftButton = await findAllByText('Save Draft'); - fireEvent.click(saveAsDraftButton); + fireEvent.click(saveAsDraftButton[0]); await waitFor(() => { - expect(getByText('Save Incomplete Project as a Draft')).toBeVisible(); + expect(getByLabelText('Draft Name *')).toBeVisible(); }); fireEvent.change(getByLabelText('Draft Name *'), { target: { value: 'draft name' } }); @@ -423,7 +387,7 @@ describe('CreateProjectPage', () => { fireEvent.click(getByText('Save')); await waitFor(() => { - expect(queryByText('Save Incomplete Project as a Draft')).not.toBeInTheDocument(); + expect(queryByLabelText('Draft Name *')).not.toBeInTheDocument(); }); }); }); diff --git a/app/src/features/projects/create/CreateProjectPage.tsx b/app/src/features/projects/create/CreateProjectPage.tsx index b3f28d946f..8fee0054f3 100644 --- a/app/src/features/projects/create/CreateProjectPage.tsx +++ b/app/src/features/projects/create/CreateProjectPage.tsx @@ -1,67 +1,32 @@ import Box from '@material-ui/core/Box'; -import Breadcrumbs from '@material-ui/core/Breadcrumbs'; import Button from '@material-ui/core/Button'; import CircularProgress from '@material-ui/core/CircularProgress'; import Container from '@material-ui/core/Container'; -import Link from '@material-ui/core/Link'; +import Paper from '@material-ui/core/Paper'; import { Theme } from '@material-ui/core/styles/createMuiTheme'; import makeStyles from '@material-ui/core/styles/makeStyles'; import Typography from '@material-ui/core/Typography'; -import ArrowBack from '@material-ui/icons/ArrowBack'; import EditDialog from 'components/dialog/EditDialog'; import { IErrorDialogProps } from 'components/dialog/ErrorDialog'; -import StepperWizard, { IStepperWizardStep } from 'components/stepper-wizard/StepperWizard'; import { DATE_FORMAT } from 'constants/dateTimeFormats'; import { CreateProjectDraftI18N, CreateProjectI18N } from 'constants/i18n'; import { DialogContext } from 'contexts/dialogContext'; -import { - ProjectCoordinatorInitialValues, - ProjectCoordinatorYupSchema -} from 'features/projects/components/ProjectCoordinatorForm'; -import { - ProjectDetailsFormInitialValues, - ProjectDetailsFormYupSchema -} from 'features/projects/components/ProjectDetailsForm'; import ProjectDraftForm, { IProjectDraftForm, - ProjectDraftFormInitialValues, ProjectDraftFormYupSchema } from 'features/projects/components/ProjectDraftForm'; -import { - ProjectFundingFormInitialValues, - ProjectFundingFormYupSchema -} from 'features/projects/components/ProjectFundingForm'; -import { ProjectIUCNFormInitialValues, ProjectIUCNFormYupSchema } from 'features/projects/components/ProjectIUCNForm'; -import { - ProjectLocationFormInitialValues, - ProjectLocationFormYupSchema -} from 'features/projects/components/ProjectLocationForm'; -import { - ProjectObjectivesFormInitialValues, - ProjectObjectivesFormYupSchema -} from 'features/projects/components/ProjectObjectivesForm'; -import { - ProjectPartnershipsFormInitialValues, - ProjectPartnershipsFormYupSchema -} from 'features/projects/components/ProjectPartnershipsForm'; -import ProjectPermitForm, { - ProjectPermitFormInitialValues, - ProjectPermitFormYupSchema -} from 'features/projects/components/ProjectPermitForm'; import { FormikProps } from 'formik'; import * as History from 'history'; import { APIError } from 'hooks/api/useAxios'; import { useBiohubApi } from 'hooks/useBioHubApi'; +import useDataLoader from 'hooks/useDataLoader'; import { useQuery } from 'hooks/useQuery'; -import { IGetAllCodeSetsResponse } from 'interfaces/useCodesApi.interface'; -import { IGetNonSamplingPermit } from 'interfaces/usePermitApi.interface'; import { ICreateProjectRequest } from 'interfaces/useProjectApi.interface'; -import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'; +import React, { useContext, useEffect, useRef, useState } from 'react'; import { useHistory } from 'react-router'; import { Prompt } from 'react-router-dom'; -import { validateFormFieldsAndReportCompletion } from 'utils/customValidation'; -import ProjectStepComponents from 'utils/ProjectStepComponents'; import { getFormattedDate } from 'utils/Utils'; +import CreateProjectForm from './CreateProjectForm'; const useStyles = makeStyles((theme: Theme) => ({ actionButton: { @@ -70,38 +35,13 @@ const useStyles = makeStyles((theme: Theme) => ({ marginLeft: '0.5rem' } }, - breadCrumbLink: { - display: 'flex', - alignItems: 'center', - cursor: 'pointer' - }, - breadCrumbLinkIcon: { - marginRight: '0.25rem' - }, - finishContainer: { - padding: theme.spacing(3), - backgroundColor: 'transparent' - }, - stepper: { - backgroundColor: 'transparent' - }, - stepTitle: { - marginBottom: '0.45rem' - }, - stepperContainer: { - display: 'flex', - flex: '1 1 auto', - overflowX: 'hidden' - }, - stepperNav: { - flex: '0 0 auto', - width: '33.333%' - }, - stepperContent: {} + pageTitleContainer: { + '& h1': { + marginBottom: theme.spacing(1) + } + } })); -const NUM_ALL_PROJECT_STEPS = 8; - /** * Page for creating a new project. * @@ -116,32 +56,39 @@ const CreateProjectPage: React.FC = () => { const queryParams = useQuery(); - const [codes, setCodes] = useState(); - const [nonSamplingPermits, setNonSamplingPermits] = useState((null as unknown) as []); - const [isLoadingCodes, setIsLoadingCodes] = useState(false); - const [isLoadingNonSamplingPermits, setIsLoadingNonSamplingPermits] = useState(false); - const [hasLoadedDraftData, setHasLoadedDraftData] = useState(!queryParams.draftId); - - // Tracks the active step # - const [activeStep, setActiveStep] = useState(0); - - // The number of steps listed in the project creation UI - const numberOfSteps = NUM_ALL_PROJECT_STEPS; - - // All possible step forms, and their current state - const [stepForms, setStepForms] = useState([]); - // Reference to pass to the formik component in order to access its state at any time // Used by the draft logic to fetch the values of a step form that has not been validated/completed - const formikRef = useRef>(null); - - const [showFormFieldValidationErrors, setShowFormFieldValidationErrors] = useState(null); + const formikRef = useRef>(null); // Ability to bypass showing the 'Are you sure you want to cancel' dialog const [enableCancelCheck, setEnableCancelCheck] = useState(true); const dialogContext = useContext(DialogContext); + const codesDataLoader = useDataLoader(() => biohubApi.codes.getAllCodeSets()); + codesDataLoader.load(); + + const draftDataLoader = useDataLoader((draftId: number) => biohubApi.draft.getDraft(draftId)); + + if (queryParams.draftId) { + draftDataLoader.load(queryParams.draftId); + } + + useEffect(() => { + const setFormikValues = (data: ICreateProjectRequest) => { + formikRef.current?.setValues(data); + }; + + if (draftDataLoader.data?.data) { + setFormikValues(draftDataLoader.data?.data); + } + }, [draftDataLoader]); + + // Whether or not to show the 'Save as draft' dialog + const [openDraftDialog, setOpenDraftDialog] = useState(false); + + const [draft, setDraft] = useState({ id: 0, date: '' }); + const defaultCancelDialogProps = { dialogTitle: CreateProjectI18N.cancelTitle, dialogText: CreateProjectI18N.cancelText, @@ -167,286 +114,24 @@ const CreateProjectPage: React.FC = () => { } }; - // Whether or not to show the 'Save as draft' dialog - const [openDraftDialog, setOpenDraftDialog] = useState(false); - - const [draft, setDraft] = useState({ id: 0, date: '' }); - const [initialProjectFieldData, setInitialProjectFieldData] = useState({ - coordinator: ProjectCoordinatorInitialValues, - permit: ProjectPermitFormInitialValues, - project: ProjectDetailsFormInitialValues, - objectives: ProjectObjectivesFormInitialValues, - location: ProjectLocationFormInitialValues, - iucn: ProjectIUCNFormInitialValues, - funding: ProjectFundingFormInitialValues, - partnerships: ProjectPartnershipsFormInitialValues - }); - - // Get non-sampling permits that already exist in system - useEffect(() => { - const getNonSamplingPermits = async () => { - const response = await biohubApi.permit.getNonSamplingPermits(); - - if (!response) { - return; - } - - setNonSamplingPermits(() => { - setIsLoadingNonSamplingPermits(false); - return response; - }); - }; - - if (!isLoadingNonSamplingPermits && !nonSamplingPermits) { - getNonSamplingPermits(); - setIsLoadingNonSamplingPermits(true); - } - }, [biohubApi, isLoadingNonSamplingPermits, nonSamplingPermits]); - - // Get draft project fields if draft id exists - useEffect(() => { - const getDraftProjectFields = async () => { - const response = await biohubApi.draft.getDraft(queryParams.draftId); - - setHasLoadedDraftData(true); - - if (!response || !response.data) { - return; - } - - setInitialProjectFieldData(response.data); - }; - - if (hasLoadedDraftData) { - return; - } - - getDraftProjectFields(); - }, [biohubApi.draft, hasLoadedDraftData, queryParams.draftId]); - - // Get code sets - // TODO refine this call to only fetch code sets this form cares about? Or introduce caching so multiple calls is still fast? - useEffect(() => { - const getAllCodeSets = async () => { - const response = await biohubApi.codes.getAllCodeSets(); - - // TODO error handling/user messaging - Cant create a project if required code sets fail to fetch - - setCodes(() => { - setIsLoadingCodes(false); - return response; - }); - }; - - if (!isLoadingCodes && !codes) { - getAllCodeSets(); - setIsLoadingCodes(true); - } - }, [biohubApi, isLoadingCodes, codes]); - - // Initialize the forms for each step of the workflow - useEffect(() => { - if (!codes || !hasLoadedDraftData || !nonSamplingPermits) { - return; - } - - if (stepForms.length) { - return; - } - - setStepForms([ - { - stepTitle: 'Project Contact', - stepSubTitle: - 'Enter the contact information for the person directly responsible for the project. This information will be used as the primary contact should questions arise about this project.', - stepContent: , - stepInitialValues: initialProjectFieldData.coordinator, - stepYupSchema: ProjectCoordinatorYupSchema, - isValid: false, - isTouched: false - }, - { - stepTitle: 'Project Permits', - stepSubTitle: - 'Enter your scientific collection, wildlife act and/or park use permits associated with this project. Provide the last 6 digits of the permit number. The last 6 digits are those after the hyphen (e.g. for KA12-845782 enter 845782).', - stepContent: ( - { - return { value: item.permit_id, label: `${item.number} - ${item.type}` }; - }) || [] - } - /> - ), - stepInitialValues: initialProjectFieldData.permit, - stepYupSchema: ProjectPermitFormYupSchema, - isValid: true, - isTouched: false - }, - { - stepTitle: 'General Information', - stepSubTitle: 'Enter general information and details about this project.', - stepContent: , - stepInitialValues: initialProjectFieldData.project, - stepYupSchema: ProjectDetailsFormYupSchema, - isValid: false, - isTouched: false - }, - { - stepTitle: 'Objectives', - stepSubTitle: - 'Describe the objectives of the project and list any caveats, or cautionary detail to be considered when evaluating, or interpreting this project.', - stepContent: , - stepInitialValues: initialProjectFieldData.objectives, - stepYupSchema: ProjectObjectivesFormYupSchema, - isValid: false, - isTouched: false - }, - { - stepTitle: 'Locations', - stepSubTitle: 'Specify a location description and spatial boundary information for the overall project area.', - stepContent: , - stepInitialValues: initialProjectFieldData.location, - stepYupSchema: ProjectLocationFormYupSchema, - isValid: false, - isTouched: false - }, - { - stepTitle: 'IUCN Conservation Actions Classification', - stepSubTitle: `Conservation actions are specific actions or sets of tasks undertaken by project staff designed to reach each of the project's objectives.`, - stepContent: , - stepInitialValues: initialProjectFieldData.iucn, - stepYupSchema: ProjectIUCNFormYupSchema, - isValid: true, - isTouched: false - }, - { - stepTitle: 'Funding', - stepSubTitle: - 'Specify funding sources for the project. Dollar amounts are not intended to be exact, please round to the nearest 100.', - stepContent: , - stepInitialValues: initialProjectFieldData.funding, - stepYupSchema: ProjectFundingFormYupSchema, - isValid: true, - isTouched: false - }, - { - stepTitle: 'Partnerships', - stepSubTitle: - 'Specify any indigenous partnerships for the project and/or any other partnerships that have not been previously identified in the funding sources section above.', - stepContent: , - stepInitialValues: initialProjectFieldData.partnerships, - stepYupSchema: ProjectPartnershipsFormYupSchema, - isValid: true, - isTouched: false - } - ]); - }, [codes, stepForms, initialProjectFieldData, hasLoadedDraftData, nonSamplingPermits]); - - /** - * Return true if the step form fields are valid, false otherwise. - * - * @return {*} {Promise} - */ - const isStepFormValid = useCallback(async (): Promise => { - if (!formikRef.current) { - return false; - } - - return validateFormFieldsAndReportCompletion(formikRef.current?.values, formikRef.current?.validateForm); - }, [formikRef]); - - const updateSteps = useCallback(async () => { - if (!formikRef?.current) { - return; - } - - const isValid = await isStepFormValid(); - - setStepForms((currentStepForms) => { - const updatedStepForms = [...currentStepForms]; - updatedStepForms[activeStep].stepInitialValues = formikRef.current?.values; - updatedStepForms[activeStep].isValid = isValid; - updatedStepForms[activeStep].isTouched = true; - return updatedStepForms; + const showDraftErrorDialog = (textDialogProps?: Partial) => { + dialogContext.setErrorDialog({ + dialogTitle: CreateProjectDraftI18N.draftErrorTitle, + dialogText: CreateProjectDraftI18N.draftErrorText, + ...defaultErrorDialogProps, + ...textDialogProps, + open: true }); - }, [activeStep, formikRef, isStepFormValid]); - - const handleSaveAndChangeStep = async (stepIndex: number) => { - await updateSteps(); - goToStep(stepIndex); - }; - - const handleSubmitProject = async () => { - await updateSteps(); - - const invalidStepIndex = getFirstInvalidFormStep(); - - // Check if any step is invalid in project workflow - const projectInvalid = invalidStepIndex >= 0; - - if (projectInvalid) { - // Automatically change to the invalid step - setActiveStep(invalidStepIndex); - // Indicate that the invalid step show run its field validation, to highlight the invalid fields - setShowFormFieldValidationErrors(invalidStepIndex); - return; - } - - await handleProjectCreation(); }; - useEffect(() => { - if (!formikRef?.current) { - return; - } - - if (showFormFieldValidationErrors !== activeStep) { - return; - } - - setShowFormFieldValidationErrors(null); - - // Submit the form, which will run the validation to indicate which fields are invalid - formikRef.current.submitForm(); - - // Update the step form isValid/isTouched - setStepForms((currentStepForms) => { - const updatedStepForms = [...currentStepForms]; - updatedStepForms[activeStep].isValid = false; - updatedStepForms[activeStep].isTouched = true; - return updatedStepForms; + const showCreateErrorDialog = (textDialogProps?: Partial) => { + dialogContext.setErrorDialog({ + dialogTitle: CreateProjectI18N.createErrorTitle, + dialogText: CreateProjectI18N.createErrorText, + ...defaultErrorDialogProps, + ...textDialogProps, + open: true }); - }, [showFormFieldValidationErrors, setShowFormFieldValidationErrors, formikRef, activeStep, updateSteps]); - - const handleSaveAndNext = async () => { - await updateSteps(); - goToNextStep(); - }; - - const handleSaveAndPrevious = async () => { - await updateSteps(); - goToPreviousStep(); - }; - - const goToNextStep = () => { - if (activeStep === numberOfSteps - 1) { - return; - } - - setActiveStep((prevActiveStep) => prevActiveStep + 1); - }; - - const goToPreviousStep = () => { - if (activeStep === 0) { - return; - } - - setActiveStep((prevActiveStep) => prevActiveStep - 1); - }; - - const goToStep = (stepIndex: number) => { - setActiveStep(stepIndex); }; const handleCancel = () => { @@ -461,16 +146,7 @@ const CreateProjectPage: React.FC = () => { // Get the form data for all steps // Fetch the data from the formikRef for whichever step is the active step // Why? WIP changes to the active step will not yet be updated into its respective stepForms[n].stepInitialValues - const draftFormData = { - coordinator: (activeStep === 0 && formikRef?.current?.values) || stepForms[0].stepInitialValues, - permit: (activeStep === 1 && formikRef?.current?.values) || stepForms[1].stepInitialValues, - project: (activeStep === 2 && formikRef?.current?.values) || stepForms[2].stepInitialValues, - objectives: (activeStep === 3 && formikRef?.current?.values) || stepForms[3].stepInitialValues, - location: (activeStep === 4 && formikRef?.current?.values) || stepForms[4].stepInitialValues, - iucn: (activeStep === 5 && formikRef?.current?.values) || stepForms[5].stepInitialValues, - funding: (activeStep === 6 && formikRef?.current?.values) || stepForms[6].stepInitialValues, - partnerships: (activeStep === 7 && formikRef?.current?.values) || stepForms[7].stepInitialValues - }; + const draftFormData = formikRef?.current?.values; const draftId = Number(queryParams.draftId) || draft?.id; @@ -505,46 +181,6 @@ const CreateProjectPage: React.FC = () => { } }; - /** - * Returns the step index for the first invalid form step, or `-1` if all steps are valid - * - * @return {*} {number} - */ - const getFirstInvalidFormStep = (): number => { - for (let i = 0; i < stepForms.length; i++) { - if (!stepForms[i].isValid) { - return i; - } - } - - // All steps are valid - return -1; - }; - - /** - * Handle project creation. - */ - const handleProjectCreation = async () => { - try { - await createProject({ - coordinator: stepForms[0].stepInitialValues, - permit: stepForms[1].stepInitialValues, - project: stepForms[2].stepInitialValues, - objectives: stepForms[3].stepInitialValues, - location: stepForms[4].stepInitialValues, - iucn: stepForms[5].stepInitialValues, - funding: stepForms[6].stepInitialValues, - partnerships: stepForms[7].stepInitialValues - }); - } catch (error) { - showCreateErrorDialog({ - dialogTitle: 'Error Creating Project', - dialogError: (error as APIError)?.message, - dialogErrorDetails: (error as APIError)?.errors - }); - } - }; - /** * Deletes the draft record used when creating this project, if one exists. * @@ -586,30 +222,6 @@ const CreateProjectPage: React.FC = () => { history.push(`/admin/projects/${response.id}`); }; - const showDraftErrorDialog = (textDialogProps?: Partial) => { - dialogContext.setErrorDialog({ - dialogTitle: CreateProjectDraftI18N.draftErrorTitle, - dialogText: CreateProjectDraftI18N.draftErrorText, - ...defaultErrorDialogProps, - ...textDialogProps, - open: true - }); - }; - - const showCreateErrorDialog = (textDialogProps?: Partial) => { - dialogContext.setErrorDialog({ - dialogTitle: CreateProjectI18N.createErrorTitle, - dialogText: CreateProjectI18N.createErrorText, - ...defaultErrorDialogProps, - ...textDialogProps, - open: true - }); - }; - - if (!stepForms.length) { - return ; - } - /** * Intercepts all navigation attempts (when used with a `Prompt`). * @@ -636,45 +248,55 @@ const CreateProjectPage: React.FC = () => { return true; }; + if (!codesDataLoader.data) { + return ; + } + return ( <> , initialValues: { - draft_name: - (activeStep === 2 && formikRef.current?.values.project_name) || - stepForms[2].stepInitialValues.project_name || - ProjectDraftFormInitialValues.draft_name + draft_name: formikRef.current?.values.project.project_name || '' }, validationSchema: ProjectDraftFormYupSchema }} onCancel={() => setOpenDraftDialog(false)} onSave={(values) => handleSubmitDraft(values)} /> - - - - - - - Cancel and Exit - - - - - Create Project - + + + + + + Create Project + + Configure and submit a new species inventory project + + + + + + @@ -683,19 +305,18 @@ const CreateProjectPage: React.FC = () => { - - - + + + + + + ); }; diff --git a/app/src/features/projects/create/__snapshots__/CreateProjectPage.test.tsx.snap b/app/src/features/projects/create/__snapshots__/CreateProjectPage.test.tsx.snap deleted file mode 100644 index 9809bffa48..0000000000 --- a/app/src/features/projects/create/__snapshots__/CreateProjectPage.test.tsx.snap +++ /dev/null @@ -1,998 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CreateProjectPage renders the initial default page correctly 1`] = ` - -
-
- -
-

- Create Project -

- -
-
-
- - Draft saved on - -
-
-
-
-
-
-
- - - - - - -

- Project Contact -

-
-
-
-
-
- -
-
- - - - - - -

- Project Permits -

-
-
-
-
-
- -
-
- - - - - - -

- General Information -

-
-
-
-
-
- -
-
- - - - - - -

- Objectives -

-
-
-
-
-
- -
-
- - - - - - -

- Locations -

-
-
-
-
-
- -
-
- - - - - - -

- IUCN Conservation Actions Classification -

-
-
-
-
-
- -
-
- - - - - - -

- Funding -

-
-
-
-
-
- -
-
- - - - - - -

- Partnerships -

-
-
-
-
-
-
-
-
-
-
-
-
-
-

- Project Contact -

-
-

- Enter the contact information for the person directly responsible for the project. This information will be used as the primary contact should questions arise about this project. -

-
-
-
-
-
-
- -
- - -
-
-
-
-
- -
- - -
-
-
-
-
- -
- - -
-
-
-
- -
-
-
-
- - Share Contact Details - -

- Do you want the project contact’s name and email address visible to the public? -

-
-
- - -

-

-
-
-
-
-
-
-
-
-
-
-
-
-
- - - -
-
-
-
-
-
- , - -`; diff --git a/app/src/features/projects/list/ProjectsListPage.test.tsx b/app/src/features/projects/list/ProjectsListPage.test.tsx index 138a81e494..85c7e3a619 100644 --- a/app/src/features/projects/list/ProjectsListPage.test.tsx +++ b/app/src/features/projects/list/ProjectsListPage.test.tsx @@ -89,7 +89,7 @@ describe('ProjectsListPage', () => { }); }); - test('renders with a proper list of projects when published and completed', async () => { + test('renders with a proper list of projects when completed', async () => { mockBiohubApi().project.getProjectsList.mockResolvedValue([ { id: 1, @@ -99,7 +99,6 @@ describe('ProjectsListPage', () => { coordinator_agency: 'contact agency', project_type: 'project type', permits_list: '1, 2, 3', - publish_status: 'Published', completion_status: 'Completed' } ]); @@ -112,12 +111,11 @@ describe('ProjectsListPage', () => { await waitFor(() => { expect(getByTestId('project-table')).toBeInTheDocument(); - expect(getByText('Published')).toBeInTheDocument(); expect(getByText('Completed')).toBeInTheDocument(); }); }); - test('renders with a proper list of projects when Unpublished and active', async () => { + test('renders with a proper list of projects when active', async () => { mockBiohubApi().project.getProjectsList.mockResolvedValue([ { id: 1, @@ -127,7 +125,6 @@ describe('ProjectsListPage', () => { coordinator_agency: 'contact agency', project_type: 'project type', permits_list: '1, 2, 3', - publish_status: 'Unpublished', completion_status: 'Active' } ]); @@ -140,7 +137,6 @@ describe('ProjectsListPage', () => { await waitFor(() => { expect(getByTestId('project-table')).toBeInTheDocument(); - expect(getByText('Unpublished')).toBeInTheDocument(); expect(getByText('Active')).toBeInTheDocument(); }); }); @@ -244,7 +240,6 @@ describe('ProjectsListPage', () => { coordinator_agency: 'contact agency', project_type: 'project type', permits_list: '1, 2, 3', - publish_status: 'Published', completion_status: 'Completed' } ]); diff --git a/app/src/features/projects/list/ProjectsListPage.tsx b/app/src/features/projects/list/ProjectsListPage.tsx index e18cac048b..bb6d965ee0 100644 --- a/app/src/features/projects/list/ProjectsListPage.tsx +++ b/app/src/features/projects/list/ProjectsListPage.tsx @@ -55,12 +55,9 @@ const useStyles = makeStyles((theme: Theme) => ({ chipActive: { backgroundColor: theme.palette.success.main }, - chipPublishedCompleted: { + chipCompleted: { backgroundColor: theme.palette.success.main }, - chipUnpublished: { - backgroundColor: theme.palette.text.disabled - }, chipDraft: { backgroundColor: theme.palette.info.main } @@ -91,18 +88,12 @@ const ProjectsListPage: React.FC = () => { let chipLabel; let chipStatusClass; - if (ProjectStatusType.UNPUBLISHED === status_name) { - chipLabel = 'Unpublished'; - chipStatusClass = classes.chipUnpublished; - } else if (ProjectStatusType.PUBLISHED === status_name) { - chipLabel = 'Published'; - chipStatusClass = classes.chipPublishedCompleted; - } else if (ProjectStatusType.ACTIVE === status_name) { + if (ProjectStatusType.ACTIVE === status_name) { chipLabel = 'Active'; chipStatusClass = classes.chipActive; } else if (ProjectStatusType.COMPLETED === status_name) { chipLabel = 'Completed'; - chipStatusClass = classes.chipPublishedCompleted; + chipStatusClass = classes.chipCompleted; } else if (ProjectStatusType.DRAFT === status_name) { chipLabel = 'Draft'; chipStatusClass = classes.chipDraft; @@ -262,7 +253,6 @@ const ProjectsListPage: React.FC = () => { Start Date End Date Status - Publish Status @@ -285,7 +275,6 @@ const ProjectsListPage: React.FC = () => { {getChipIcon('Draft')} - {getChipIcon('Unpublished')} ))} {projects?.map((row) => ( @@ -301,12 +290,10 @@ const ProjectsListPage: React.FC = () => { {row.project_type} - {row.permits_list} {row.coordinator_agency} {getFormattedDate(DATE_FORMAT.ShortMediumDateFormat, row.start_date)} {getFormattedDate(DATE_FORMAT.ShortMediumDateFormat, row.end_date)} {getChipIcon(row.completion_status)} - {getChipIcon(row.publish_status)} ))} diff --git a/app/src/features/projects/view/ProjectDetails.tsx b/app/src/features/projects/view/ProjectDetails.tsx index 49f3453858..bb6d1b2d25 100644 --- a/app/src/features/projects/view/ProjectDetails.tsx +++ b/app/src/features/projects/view/ProjectDetails.tsx @@ -10,7 +10,6 @@ import ProjectObjectives from 'features/projects/view/components/ProjectObjectiv import { IGetAllCodeSetsResponse } from 'interfaces/useCodesApi.interface'; import { IGetProjectForViewResponse } from 'interfaces/useProjectApi.interface'; import React from 'react'; -import ProjectPermits from './components/ProjectPermits'; export interface IProjectDetailsProps { projectForViewData: IGetProjectForViewResponse; @@ -29,7 +28,9 @@ const ProjectDetails: React.FC = (props) => { return ( <> - Project Details + + Project Details + @@ -42,9 +43,6 @@ const ProjectDetails: React.FC = (props) => { - - - diff --git a/app/src/features/projects/view/ProjectHeader.tsx b/app/src/features/projects/view/ProjectHeader.tsx index 21ac758850..8643412f7b 100644 --- a/app/src/features/projects/view/ProjectHeader.tsx +++ b/app/src/features/projects/view/ProjectHeader.tsx @@ -8,14 +8,13 @@ import Link from '@material-ui/core/Link'; import Paper from '@material-ui/core/Paper'; import { Theme } from '@material-ui/core/styles/createMuiTheme'; import makeStyles from '@material-ui/core/styles/makeStyles'; -import Tooltip from '@material-ui/core/Tooltip'; import Typography from '@material-ui/core/Typography'; import { mdiTrashCanOutline } from '@mdi/js'; import Icon from '@mdi/react'; import clsx from 'clsx'; import { IErrorDialogProps } from 'components/dialog/ErrorDialog'; import { DATE_FORMAT } from 'constants/dateTimeFormats'; -import { DeleteProjectI18N, PublishProjectI18N } from 'constants/i18n'; +import { DeleteProjectI18N } from 'constants/i18n'; import { ProjectStatusType } from 'constants/misc'; import { SYSTEM_ROLE } from 'constants/roles'; import { AuthStateContext } from 'contexts/authStateContext'; @@ -74,7 +73,7 @@ const useStyles = makeStyles((theme: Theme) => ({ export interface IProjectHeaderProps { projectWithDetails: IGetProjectForViewResponse; - refresh: () => void; + refresh?: () => void; } /** @@ -84,7 +83,7 @@ export interface IProjectHeaderProps { * @return {*} */ const ProjectHeader: React.FC = (props) => { - const { projectWithDetails, refresh } = props; + const { projectWithDetails } = props; const classes = useStyles(); const history = useHistory(); @@ -116,27 +115,6 @@ const ProjectHeader: React.FC = (props) => { } }; - const publishProject = async (publish: boolean) => { - if (!projectWithDetails) { - return; - } - - try { - const response = await biohubApi.project.publishProject(projectWithDetails.id, publish); - - if (!response) { - showPublishErrorDialog({ open: true }); - return; - } - - await refresh(); - } catch (error) { - const apiError = error as APIError; - showPublishErrorDialog({ dialogText: apiError.message, open: true }); - return error; - } - }; - const showDeleteProjectDialog = () => { dialogContext.setYesNoDialog({ ...defaultYesNoDialogProps, @@ -169,20 +147,10 @@ const ProjectHeader: React.FC = (props) => { } }; - const publishErrorDialogProps = { - ...deleteErrorDialogProps, - dialogTitle: PublishProjectI18N.publishErrorTitle, - dialogText: PublishProjectI18N.publishErrorText - }; - const showDeleteErrorDialog = (textDialogProps?: Partial) => { dialogContext.setErrorDialog({ ...deleteErrorDialogProps, ...textDialogProps, open: true }); }; - const showPublishErrorDialog = (textDialogProps?: Partial) => { - dialogContext.setErrorDialog({ ...publishErrorDialogProps, ...textDialogProps, open: true }); - }; - const getChipIcon = (status_name: string) => { let chipLabel; let chipStatusClass; @@ -203,10 +171,6 @@ const ProjectHeader: React.FC = (props) => { SYSTEM_ROLE.SYSTEM_ADMIN, SYSTEM_ROLE.PROJECT_CREATOR ]); - // Enable delete button if you a system admin OR a project admin and the project is not published - const enableDeleteProjectButton = - keycloakWrapper?.hasSystemRole([SYSTEM_ROLE.SYSTEM_ADMIN]) || - (keycloakWrapper?.hasSystemRole([SYSTEM_ROLE.PROJECT_CREATOR]) && !projectWithDetails.project.publish_date); return ( @@ -266,31 +230,10 @@ const ProjectHeader: React.FC = (props) => { onClick={() => history.push('users')}> Manage Project Team - {showDeleteProjectButton && ( - - <> - - - - - + + + )} diff --git a/app/src/features/projects/view/ProjectPage.test.tsx b/app/src/features/projects/view/ProjectPage.test.tsx index e46ef12ea8..e129082533 100644 --- a/app/src/features/projects/view/ProjectPage.test.tsx +++ b/app/src/features/projects/view/ProjectPage.test.tsx @@ -272,13 +272,13 @@ describe('ProjectPage', () => { }); }); - it('sees delete project button as enabled when accessing an unpublished project as a project administrator', async () => { + it('sees delete project button as enabled when accessing a project as a project administrator', async () => { mockBiohubApi().codes.getAllCodeSets.mockResolvedValue({ activity: [{ id: 1, name: 'activity 1' }] } as any); mockBiohubApi().project.getProjectForView.mockResolvedValue({ ...getProjectForViewResponse, - project: { ...getProjectForViewResponse.project, publish_date: '' } + project: { ...getProjectForViewResponse.project } }); mockBiohubApi().project.deleteProject.mockResolvedValue(true); @@ -306,40 +306,6 @@ describe('ProjectPage', () => { expect(getByTestId('delete-project-button')).toBeEnabled(); }); - it('sees delete project button as disabled when accessing a published project as a project administrator', async () => { - mockBiohubApi().codes.getAllCodeSets.mockResolvedValue({ - activity: [{ id: 1, name: 'activity 1' }] - } as any); - mockBiohubApi().project.getProjectForView.mockResolvedValue({ - ...getProjectForViewResponse, - project: { ...getProjectForViewResponse.project, publish_date: '2021-07-07' } - }); - mockBiohubApi().project.deleteProject.mockResolvedValue(true); - - const authState = { - keycloakWrapper: { - ...defaultAuthState.keycloakWrapper, - systemRoles: [SYSTEM_ROLE.PROJECT_CREATOR] as string[], - hasSystemRole: jest.fn().mockReturnValueOnce(true).mockReturnValueOnce(false).mockReturnValueOnce(true) - } - }; - - const { getByTestId, findByText } = render( - - - - - - - - ); - - const projectHeaderText = await findByText('Test Project Name', { selector: 'h1 span' }); - expect(projectHeaderText).toBeVisible(); - - expect(getByTestId('delete-project-button')).toBeDisabled(); - }); - it('does not see the delete button when accessing project as non admin user', async () => { mockBiohubApi().codes.getAllCodeSets.mockResolvedValue({ activity: [{ id: 1, name: 'activity 1' }] @@ -395,147 +361,4 @@ describe('ProjectPage', () => { expect(asFragment()).toMatchSnapshot(); }); }); - - it('publishes and unpublishes a project', async () => { - mockBiohubApi().codes.getAllCodeSets.mockResolvedValue({ - activity: [{ id: 1, name: 'activity 1' }] - } as any); - mockBiohubApi().project.getProjectForView.mockResolvedValue({ - ...getProjectForViewResponse, - project: { ...getProjectForViewResponse.project, publish_date: '' } - }); - mockBiohubApi().project.publishProject.mockResolvedValue({ id: 1 }); - - const { getByTestId } = render( - - - - - - ); - - await waitFor(() => { - const publishButtonText1 = getByTestId('publish-project-button'); - expect(publishButtonText1).toBeVisible(); - expect(publishButtonText1.textContent).toEqual('Publish'); - }); - - //re-mock response to return the project with a non-null publish date - mockBiohubApi().project.getProjectForView.mockResolvedValue({ - ...getProjectForViewResponse, - project: { ...getProjectForViewResponse.project, publish_date: '2021-10-10' } - }); - - fireEvent.click(getByTestId('publish-project-button')); - - await waitFor(() => { - const publishButtonText1 = getByTestId('publish-project-button'); - expect(publishButtonText1).toBeVisible(); - expect(publishButtonText1.textContent).toEqual('Unpublish'); - }); - - //re-mock response to return the project with a null publish date - mockBiohubApi().project.getProjectForView.mockResolvedValue({ - ...getProjectForViewResponse, - project: { ...getProjectForViewResponse.project, publish_date: '' } - }); - - fireEvent.click(getByTestId('publish-project-button')); - - await waitFor(() => { - const publishButtonText1 = getByTestId('publish-project-button'); - expect(publishButtonText1).toBeVisible(); - expect(publishButtonText1.textContent).toEqual('Publish'); - }); - }); - - it('shows API error when fails to publish project', async () => { - mockBiohubApi().codes.getAllCodeSets.mockResolvedValue({ - activity: [{ id: 1, name: 'activity 1' }] - } as any); - mockBiohubApi().project.getProjectForView.mockResolvedValue({ - ...getProjectForViewResponse, - project: { ...getProjectForViewResponse.project, publish_date: '' } - }); - mockBiohubApi().project.publishProject = jest.fn(() => Promise.reject(new Error('API Error is Here'))); - - const { getByTestId, queryByText, getAllByRole } = render( - - - - - - ); - - await waitFor(() => { - const publishButtonText1 = getByTestId('publish-project-button'); - expect(publishButtonText1).toBeVisible(); - expect(publishButtonText1.textContent).toEqual('Publish'); - }); - - //re-mock response to return the project with a non-null publish date - mockBiohubApi().project.getProjectForView.mockResolvedValue({ - ...getProjectForViewResponse, - project: { ...getProjectForViewResponse.project, publish_date: '2021-10-10' } - }); - - fireEvent.click(getByTestId('publish-project-button')); - - await waitFor(() => { - expect(queryByText('API Error is Here')).toBeInTheDocument(); - }); - - // Get the backdrop, then get the firstChild because this is where the event listener is attached - //@ts-ignore - fireEvent.click(getAllByRole('presentation')[0].firstChild); - - await waitFor(() => { - expect(queryByText('API Error is Here')).toBeNull(); - }); - }); - - it('shows basic error dialog when publish project returns null response', async () => { - mockBiohubApi().codes.getAllCodeSets.mockResolvedValue({ - activity: [{ id: 1, name: 'activity 1' }] - } as any); - mockBiohubApi().project.getProjectForView.mockResolvedValue({ - ...getProjectForViewResponse, - project: { ...getProjectForViewResponse.project, publish_date: '' } - }); - mockBiohubApi().project.publishProject.mockResolvedValue(null); - - const { getByTestId, queryByText, getAllByRole } = render( - - - - - - ); - - await waitFor(() => { - const publishButtonText1 = getByTestId('publish-project-button'); - expect(publishButtonText1).toBeVisible(); - expect(publishButtonText1.textContent).toEqual('Publish'); - }); - - //re-mock response to return the project with a non-null publish date - mockBiohubApi().project.getProjectForView.mockResolvedValue({ - ...getProjectForViewResponse, - project: { ...getProjectForViewResponse.project, publish_date: '2021-10-10' } - }); - - fireEvent.click(getByTestId('publish-project-button')); - - await waitFor(() => { - expect(queryByText('Error Publishing Project')).toBeInTheDocument(); - }); - - // Get the backdrop, then get the firstChild because this is where the event listener is attached - //@ts-ignore - fireEvent.click(getAllByRole('presentation')[0].firstChild); - - await waitFor(() => { - expect(queryByText('Error Publishing Project')).toBeNull(); - }); - }); }); diff --git a/app/src/features/projects/view/__snapshots__/ProjectDetails.test.tsx.snap b/app/src/features/projects/view/__snapshots__/ProjectDetails.test.tsx.snap index a1b767563a..dba3149bb7 100644 --- a/app/src/features/projects/view/__snapshots__/ProjectDetails.test.tsx.snap +++ b/app/src/features/projects/view/__snapshots__/ProjectDetails.test.tsx.snap @@ -6,7 +6,7 @@ exports[`ProjectDetails renders correctly 1`] = ` class="MuiPaper-root MuiBox-root MuiBox-root-1 MuiPaper-elevation1 MuiPaper-rounded" >

Project Details

@@ -21,7 +21,7 @@ exports[`ProjectDetails renders correctly 1`] = ` style="justify-content: space-between;" >

General Information

@@ -142,7 +142,7 @@ exports[`ProjectDetails renders correctly 1`] = ` style="justify-content: space-between;" >

Objectives

@@ -200,35 +200,26 @@ exports[`ProjectDetails renders correctly 1`] = ` Et ad et in culpa si

-
-

- sjwer bds -

-

Project Contact

IUCN Conservation Actions Classification


-
-
-

- Project Permits -

-
- -
-
-
- - - - - - - - - - - - - -
- Number - - Type -
- 123 - - Permit type -
-
-
-
-
-
-

Funding Sources

-
-
-
-
-

Funding Sources

-
- -`; - -exports[`ProjectPermits renders correctly with sampling conducted true 1`] = ` - -
-
-

- Project Permits -

-
- -
-
-
- - - - - - - - - - - - - -
- Number - - Type -
- 123 - - Permit type -
-
-
-
-`; diff --git a/app/src/features/resources/ResourcesPage.tsx b/app/src/features/resources/ResourcesPage.tsx index ce90ab71e1..892649a435 100644 --- a/app/src/features/resources/ResourcesPage.tsx +++ b/app/src/features/resources/ResourcesPage.tsx @@ -15,7 +15,7 @@ import { ConfigContext } from 'contexts/configContext'; import React, { useContext } from 'react'; /** - * Page to display a list of resources. + * Page to display a list of resources * * @return {*} */ @@ -26,43 +26,99 @@ const ResourcesPage: React.FC = () => { const resources = [ { id: '1', - name: 'Moose Aerial StratifiedRandomBlock Composition Survey 2.5', - url: `${s3PublicHostURL}/templates/Moose_Aerial_StratifiedRandomBlock_Composition_Recruitment_Survey_2.5.xlsx`, + name: 'Moose Aerial Non-SRB Recruitment Composition Survey 1.0', + url: `${s3PublicHostURL}/templates/Moose_Aerial_NonStratifiedRandomBlock_Recruit_Comp_Survey_1.0.xlsx`, type: mdiFileExcelOutline, lastModified: 'Today', - fileSize: '115 KB' + fileSize: '145 KB' }, { id: '2', - name: 'Moose Recruitment Using Telemetry Survey 1.0', - url: `${s3PublicHostURL}/templates/Moose_Recruitment_Using_Telemetry_Survey_1.0.xlsx`, + name: 'Moose Aerial Stratified Random Block Recruitment Composition Survey 1.0', + url: `${s3PublicHostURL}/templates/Moose_Aerial_StratifiedRandomBlock_Recruit_Comp_Survey_1.0.xlsx`, type: mdiFileExcelOutline, lastModified: 'Today', - fileSize: '93 KB' + fileSize: '143 KB' }, { id: '3', - name: 'Goat Aerial Population Composition Recruitment Survey 1.4', - url: `${s3PublicHostURL}/templates/Goat_Aerial_Population_Composition_Recruitment_Survey_1.4.xlsx`, + name: 'Moose Aerial Transect Distance Sampling Survey 1.0', + url: `${s3PublicHostURL}/templates/Moose_Aerial_Transect_Distance_Sampling_Survey_1.0.xlsx`, type: mdiFileExcelOutline, lastModified: 'Today', - fileSize: '165 KB' + fileSize: '143 KB' }, { id: '4', - name: 'Sheep Aerial Population Composition Recruitment Survey 1.2', - url: `${s3PublicHostURL}/templates/Sheep_Aerial_Population_Composition_Recruitment_Survey_1.2.xlsx`, + name: 'Sheep Aerial Total Count Recruitment Composition Survey 1.0', + url: `${s3PublicHostURL}/templates/Sheep_Aerial_Population_Total_Count_Recuit_Comp_Survey_1.0.xlsx`, type: mdiFileExcelOutline, lastModified: 'Today', - fileSize: '109 KB' + fileSize: '131 KB' }, { id: '5', - name: 'Moose Summary Statistics', - url: `${s3PublicHostURL}/templates/Moose_Summary_Statistics.xlsx`, + name: 'Mountain Goat Aerial Total Count Recruitment Composition Survey 1.0', + url: `${s3PublicHostURL}/templates/Goat_Aerial_Population_Total_Count_Recuit_Comp_Survey_1.0.xlsx`, type: mdiFileExcelOutline, lastModified: 'Today', - fileSize: '10 KB' + fileSize: '100 KB' + }, + { + id: '6', + name: 'Moose Summary Results Template 1.0', + url: `${s3PublicHostURL}/templates/Moose_Summary_Results_1.0.xlsx`, + type: mdiFileExcelOutline, + lastModified: 'Today', + fileSize: '27 KB' + }, + { + id: '7', + name: 'Sheep Summary Results Template 1.0', + url: `${s3PublicHostURL}/templates/Sheep_Summary_Results_1.0.xlsx`, + type: mdiFileExcelOutline, + lastModified: 'Today', + fileSize: '24 KB' + }, + { + id: '8', + name: 'Goat Summary Results Template 1.0', + url: `${s3PublicHostURL}/templates/Goat_Summary_Results_1.0.xlsx`, + type: mdiFileExcelOutline, + lastModified: 'Today', + fileSize: '27 KB' + }, + { + id: '9', + name: 'Elk Summary Results Template 1.0', + url: `${s3PublicHostURL}/templates/Elk_Summary_Results_1.0.xlsx`, + type: mdiFileExcelOutline, + lastModified: 'Today', + fileSize: '24 KB' + }, + { + id: '10', + name: 'Elk Aerial Stratified Random Block Recruit Composition Survey 1.0', + url: `${s3PublicHostURL}/templates/Elk_Aerial_StratifiedRandomBlock_Recruit_Comp_Survey_1.0.xlsx`, + type: mdiFileExcelOutline, + lastModified: 'Today', + fileSize: '122 KB' + }, + { + id: '11', + name: 'Elk Aerial Non Stratified Random Block Recruit Composition Survey 1.0', + url: `${s3PublicHostURL}/templates/Elk_Aerial_NonStratifiedRandomBlock_Recruit_Comp_Survey_1.0.xlsx`, + type: mdiFileExcelOutline, + lastModified: 'Today', + fileSize: '120 KB' + }, + { + id: '12', + name: 'Elk Aerial Transect Distance Sampling Recruit Composition Survey 1.0', + url: `${s3PublicHostURL}/templates/Elk_Aerial_Transect_DistanceSampling_Recruit_Comp_Survey_1.0.xlsx`, + type: mdiFileExcelOutline, + lastModified: 'Today', + fileSize: '114 KB' } ]; diff --git a/app/src/features/surveys/CreateSurveyPage.test.tsx b/app/src/features/surveys/CreateSurveyPage.test.tsx index 212bb838cd..e528d8573e 100644 --- a/app/src/features/surveys/CreateSurveyPage.test.tsx +++ b/app/src/features/surveys/CreateSurveyPage.test.tsx @@ -195,8 +195,8 @@ describe('CreateSurveyPage', () => { }); fireEvent.click(getByText('Cancel')); await waitFor(() => { - expect(getByText('Cancel Create Survey')).toBeInTheDocument(); - expect(getByText('Are you sure you want to cancel?')).toBeInTheDocument(); + expect(getByText('Cancel Survey Creation')).toBeInTheDocument(); + expect(getByText('Are you sure you want to cancel?', { exact: false })).toBeInTheDocument(); }); fireEvent.click(getByTestId('yes-button')); await waitFor(() => { @@ -245,8 +245,8 @@ describe('CreateSurveyPage', () => { }); fireEvent.click(getByText('Cancel')); await waitFor(() => { - expect(getByText('Cancel Create Survey')).toBeInTheDocument(); - expect(getByText('Are you sure you want to cancel?')).toBeInTheDocument(); + expect(getByText('Cancel Survey Creation')).toBeInTheDocument(); + expect(getByText('Are you sure you want to cancel?', { exact: false })).toBeInTheDocument(); }); fireEvent.click(getByTestId('no-button')); await waitFor(() => { @@ -254,8 +254,8 @@ describe('CreateSurveyPage', () => { }); fireEvent.click(getByText('Cancel')); await waitFor(() => { - expect(getByText('Cancel Create Survey')).toBeInTheDocument(); - expect(getByText('Are you sure you want to cancel?')).toBeInTheDocument(); + expect(getByText('Cancel Survey Creation')).toBeInTheDocument(); + expect(getByText('Are you sure you want to cancel?', { exact: false })).toBeInTheDocument(); }); // Get the backdrop, then get the firstChild because this is where the event listener is attached //@ts-ignore diff --git a/app/src/features/surveys/CreateSurveyPage.tsx b/app/src/features/surveys/CreateSurveyPage.tsx index 3d8c77c14d..912e9714dc 100644 --- a/app/src/features/surveys/CreateSurveyPage.tsx +++ b/app/src/features/surveys/CreateSurveyPage.tsx @@ -21,11 +21,7 @@ import { APIError } from 'hooks/api/useAxios'; import { useBiohubApi } from 'hooks/useBioHubApi'; import { IGetAllCodeSetsResponse } from 'interfaces/useCodesApi.interface'; import { IGetProjectForViewResponse } from 'interfaces/useProjectApi.interface'; -import { - ICreateSurveyRequest, - ISurveyAvailableFundingSources, - ISurveyPermits -} from 'interfaces/useSurveyApi.interface'; +import { ICreateSurveyRequest, ISurveyAvailableFundingSources } from 'interfaces/useSurveyApi.interface'; import moment from 'moment'; import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'; import { Prompt, useHistory, useParams } from 'react-router'; @@ -61,23 +57,10 @@ const useStyles = makeStyles((theme: Theme) => ({ breadCrumbLinkIcon: { marginRight: '0.25rem' }, - finishContainer: { - padding: theme.spacing(3), - backgroundColor: 'transparent' - }, - surveySection: { - marginTop: theme.spacing(4), - marginBottom: theme.spacing(5), - - '&:last-child': { - marginBottom: 0 - }, - '&:first-child': { - marginTop: 0 - } - }, sectionDivider: { - height: '1px' + height: '1px', + marginTop: theme.spacing(5), + marginBottom: theme.spacing(5) } })); @@ -96,7 +79,6 @@ const CreateSurveyPage = () => { const [projectWithDetails, setProjectWithDetails] = useState(null); const [isLoadingCodes, setIsLoadingCodes] = useState(false); const [codes, setCodes] = useState(); - const [surveyPermits, setSurveyPermits] = useState([]); const [surveyFundingSources, setSurveyFundingSources] = useState([]); const [formikRef] = useState(useRef>(null)); @@ -148,7 +130,7 @@ const CreateSurveyPage = () => { DATE_FORMAT.ShortDateFormat, `Survey start date cannot be before ${getFormattedDate(DATE_FORMAT.ShortMediumDateFormat, DATE_LIMIT.min)}` ) - .required('Required'), + .required('Start Date is Required'), end_date: yup .string() .isValidDateString() @@ -190,18 +172,16 @@ const CreateSurveyPage = () => { }, [urlParams, biohubApi.codes, isLoadingCodes, codes]); const getProject = useCallback(async () => { - const [projectWithDetailsResponse, surveyPermitsResponse, surveyFundingSourcesResponse] = await Promise.all([ + const [projectWithDetailsResponse, surveyFundingSourcesResponse] = await Promise.all([ biohubApi.project.getProjectForView(urlParams['id']), - biohubApi.survey.getSurveyPermits(urlParams['id']), biohubApi.survey.getAvailableSurveyFundingSources(urlParams['id']) ]); - if (!projectWithDetailsResponse || !surveyPermitsResponse || !surveyFundingSourcesResponse) { + if (!projectWithDetailsResponse || !surveyFundingSourcesResponse) { // TODO error handling/messaging return; } - setSurveyPermits(surveyPermitsResponse); setSurveyFundingSources(surveyFundingSourcesResponse); setProjectWithDetails(projectWithDetailsResponse); }, [biohubApi.project, biohubApi.survey, urlParams]); @@ -316,7 +296,7 @@ const CreateSurveyPage = () => { Create Survey - + { summary="" component={ { - return { value: item.permit_number, label: `${item.permit_number} - ${item.permit_type}` }; - }) || [] - } funding_sources={ surveyFundingSources?.map((item) => { return { @@ -358,6 +333,8 @@ const CreateSurveyPage = () => { + + { summary="" component={}> + + + + + - - - - - diff --git a/app/src/features/projects/components/ProjectPermitForm.test.tsx b/app/src/features/surveys/SurveyPermitForm.test.tsx similarity index 51% rename from app/src/features/projects/components/ProjectPermitForm.test.tsx rename to app/src/features/surveys/SurveyPermitForm.test.tsx index 12470dbf6d..f7af951f3b 100644 --- a/app/src/features/projects/components/ProjectPermitForm.test.tsx +++ b/app/src/features/surveys/SurveyPermitForm.test.tsx @@ -1,22 +1,22 @@ import { fireEvent, render, waitFor } from '@testing-library/react'; import { Formik } from 'formik'; import React from 'react'; -import ProjectPermitForm, { - IProjectPermitForm, - ProjectPermitFormInitialValues, - ProjectPermitFormYupSchema -} from './ProjectPermitForm'; +import SurveyPermitForm, { + ISurveyPermitForm, + SurveyPermitFormInitialValues, + SurveyPermitFormYupSchema +} from './SurveyPermitForm'; -describe('ProjectPermitForm', () => { +describe('SurveyPermitForm', () => { it('renders correctly with default empty values', () => { const { asFragment } = render( {}}> - {() => } + {() => } ); @@ -24,27 +24,31 @@ describe('ProjectPermitForm', () => { }); it('renders correctly with existing permit values', () => { - const existingFormValues: IProjectPermitForm = { - permits: [ - { - permit_number: '123', - permit_type: 'Park Use Permit' - }, - { - permit_number: '3213123123', - permit_type: 'Scientific Fish Collection Permit' - } - ] + const existingFormValues: ISurveyPermitForm = { + permit: { + permits: [ + { + permit_id: 1, + permit_number: '123', + permit_type: 'Park Use Permit' + }, + { + permit_id: 2, + permit_number: '3213123123', + permit_type: 'Scientific Fish Collection Permit' + } + ] + } }; const { asFragment } = render( {}}> - {() => } + {() => } ); @@ -52,19 +56,27 @@ describe('ProjectPermitForm', () => { }); it('renders correctly with errors on the permit_number and permit_type fields', () => { - const existingFormValues: IProjectPermitForm = { - permits: [ - { - permit_number: '123', - permit_type: 'Scientific Fish Collection Permit' - } - ] + const existingFormValues: ISurveyPermitForm = { + permit: { + permits: [ + { + permit_id: 1, + permit_number: '123', + permit_type: 'Scientific Fish Collection Permit' + }, + { + permit_id: 2, + permit_number: '123', + permit_type: 'Scientific Fish Collection Permit' + } + ] + } }; const { asFragment } = render( { permits: [{ permit_number: true, permit_type: true }] }} onSubmit={async () => {}}> - {() => } + {() => } ); @@ -82,28 +94,32 @@ describe('ProjectPermitForm', () => { }); it('renders correctly with error on the permits field due to duplicates', () => { - const existingFormValues: IProjectPermitForm = { - permits: [ - { - permit_number: '123', - permit_type: 'Park Use Permit' - }, - { - permit_number: '123', - permit_type: 'Scientific Fish Collection Permit' - } - ] + const existingFormValues: ISurveyPermitForm = { + permit: { + permits: [ + { + permit_id: 1, + permit_number: '123', + permit_type: 'Scientific Fish Collection Permit' + }, + { + permit_id: 2, + permit_number: '123', + permit_type: 'Scientific Fish Collection Permit' + } + ] + } }; const { asFragment } = render( {}}> - {() => } + {() => } ); @@ -111,23 +127,26 @@ describe('ProjectPermitForm', () => { }); it('deletes existing permits when delete icon is clicked', async () => { - const existingFormValues: IProjectPermitForm = { - permits: [ - { - permit_number: '123', - permit_type: 'Scientific Fish Collection Permit' - } - ] + const existingFormValues: ISurveyPermitForm = { + permit: { + permits: [ + { + permit_id: 1, + permit_number: '123', + permit_type: 'Scientific Fish Collection Permit' + } + ] + } }; const { getByTestId, queryByText } = render( {}}> - {() => } + {() => } ); diff --git a/app/src/features/surveys/SurveyPermitForm.tsx b/app/src/features/surveys/SurveyPermitForm.tsx new file mode 100644 index 0000000000..b0dcba69cd --- /dev/null +++ b/app/src/features/surveys/SurveyPermitForm.tsx @@ -0,0 +1,176 @@ +import Box from '@material-ui/core/Box'; +import Button from '@material-ui/core/Button'; +import FormControl from '@material-ui/core/FormControl'; +import FormHelperText from '@material-ui/core/FormHelperText'; +import Grid from '@material-ui/core/Grid'; +import IconButton from '@material-ui/core/IconButton'; +import InputLabel from '@material-ui/core/InputLabel'; +import List from '@material-ui/core/List'; +import ListItem from '@material-ui/core/ListItem'; +import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'; +import ListItemText from '@material-ui/core/ListItemText'; +import MenuItem from '@material-ui/core/MenuItem'; +import Select from '@material-ui/core/Select'; +import { Theme } from '@material-ui/core/styles/createMuiTheme'; +import makeStyles from '@material-ui/core/styles/makeStyles'; +import Typography from '@material-ui/core/Typography'; +import { mdiPlus, mdiTrashCanOutline } from '@mdi/js'; +import Icon from '@mdi/react'; +import CustomTextField from 'components/fields/CustomTextField'; +import { FieldArray, useFormikContext } from 'formik'; +import React from 'react'; +import yup from 'utils/YupSchema'; + +const useStyles = makeStyles((theme: Theme) => ({ + permitList: { + '& li:first-of-type': { + marginTop: theme.spacing(-1.5) + }, + '& .MuiListItemSecondaryAction-root': { + top: '35px' + } + } +})); + +export interface ISurveyPermitFormArrayItem { + permit_id: number; + permit_number: string; + permit_type: string; +} + +export interface ISurveyPermitForm { + permit: { + permits: ISurveyPermitFormArrayItem[]; + }; +} + +export const SurveyPermitFormArrayItemInitialValues: ISurveyPermitFormArrayItem = { + permit_id: (null as unknown) as number, + permit_number: '', + permit_type: '' +}; + +export const SurveyPermitFormInitialValues: ISurveyPermitForm = { + permit: { + permits: [] + } +}; + +export const SurveyPermitFormYupSchema = yup.object().shape({ + permit: yup.object().shape({ + permits: yup + .array() + .of( + yup.object().shape({ + permit_id: yup.number().nullable(true), + permit_number: yup.string().max(100, 'Cannot exceed 100 characters').required('Permit Number is Required'), + permit_type: yup.string().required('Permit Type is Required') + }) + ) + .isUniquePermitNumber('Permit numbers must be unique') + }) +}); + +/** + * Create Survey - Permit section + * + * @return {*} + */ +const SurveyPermitForm: React.FC = () => { + const classes = useStyles(); + const { values, handleChange, getFieldMeta, errors } = useFormikContext(); + + return ( + ( + <> + + {values.permit.permits?.map((permit, index) => { + const permitNumberMeta = getFieldMeta(`permit.permits.[${index}].permit_number`); + const permitTypeMeta = getFieldMeta(`permit.permits.[${index}].permit_type`); + + return ( + + + + + + + + + + Permit Type + + {permitTypeMeta.touched && permitTypeMeta.error} + + + + + + + arrayHelpers.remove(index)}> + + + + + ); + })} + + {errors.permit?.permits && !Array.isArray(errors?.permit.permits) && ( + + {errors.permit.permits} + + )} + + + + + )} + /> + ); +}; + +export default SurveyPermitForm; diff --git a/app/src/features/surveys/__snapshots__/CreateSurveyPage.test.tsx.snap b/app/src/features/surveys/__snapshots__/CreateSurveyPage.test.tsx.snap index 4a674cbbd0..446c188c1a 100644 --- a/app/src/features/surveys/__snapshots__/CreateSurveyPage.test.tsx.snap +++ b/app/src/features/surveys/__snapshots__/CreateSurveyPage.test.tsx.snap @@ -3,13 +3,13 @@ exports[`CreateSurveyPage renders correctly when codes and project data are loaded 1`] = `
-
+
+ + Lead Biologist + +
- - Lead Biologist -
-
- - -
-
-
+  * + +
- -
+
+ + First Name * + + +
- -
- - Permits - -

- If a permit is required for this survey, select a permit or add new one. -

+
-
-

- OR -

-
-
+
+ +
+ + Permits + +
+
    +
    + -
    + Add New Permit + + +
-
-
+
+
+ + Funding Sources + +
- +
+
-
+
-
+

Purpose and Methodology

Purpose of Survey @@ -925,7 +821,7 @@ exports[`CreateSurveyPage renders correctly when codes and project data are load data-shrink="false" id="purpose_and_methodology.intended_outcome_id-label" > - Intended Outcomes + Intended Outcome
Survey Methodology @@ -1087,10 +983,10 @@ exports[`CreateSurveyPage renders correctly when codes and project data are load
+
+
+ + Surveyed Areas + +

+ Did you survey all areas that include your population of interest? +

- - Surveyed Areas * - -
-

- Did you survey all areas that include your population of interest? -

-
- - + -

-

+ class="MuiTouchRipple-root" + /> + + + No - only some areas were surveyed + + +

-
+
- +
- +
-

Study Area

+
-
- -
+ +
+ +
+ + Last Name * + + +
- -
+
+
+ - - Permits - -

+

+
    +
    - If a permit is required for this survey, select a permit or add new one. -

    + +
    +
+
+
+ + Funding Sources + +
-
- - Funding Sources - - -
- +
+ `; exports[`General Information Form renders correctly when errors exist 1`] = ` -
+
-
- -
+ +
+ +
-

- error on survey name field -

+ + Survey Name * + + +
+

+ error on survey name field +

+
+
@@ -1748,14 +1536,22 @@ exports[`General Information Form renders correctly when errors exist 1`] = `
+
+
+
+ + Species + +
- - Species -
-
+
+ + Lead Biologist + +
- - Lead Biologist -
-
- -
- - -
- -
-
-
+  * + +
- -
+
-

- error on biologist last name field -

+ + First Name * + + +
+

+ error on biologist first name field +

- -
- - Permits - -

- If a permit is required for this survey, select a permit or add new one. -

+
- -

- OR + error on biologist last name field

-
+
+ +
+ + Permits + +
+
    +
    + -
    + Add New Permit + + +
-
-
+
+
+ + Funding Sources + + -
- + +
`; diff --git a/app/src/features/surveys/components/__snapshots__/ProprietaryDataForm.test.tsx.snap b/app/src/features/surveys/components/__snapshots__/ProprietaryDataForm.test.tsx.snap index bed461e3e7..1a2aae7178 100644 --- a/app/src/features/surveys/components/__snapshots__/ProprietaryDataForm.test.tsx.snap +++ b/app/src/features/surveys/components/__snapshots__/ProprietaryDataForm.test.tsx.snap @@ -324,355 +324,363 @@ exports[`Proprietary Data Form renders correctly the filled component correctly
-

- Proprietary Information -

-
-
- - + +
+ + +
+
- - - -
-
- -
- - + +
+ + +
+
+
- - -
+
- -
- - -
-
-
-
-
- - Data and Information Sharing Agreement (DISA) - -

- Do you require a data and information sharing agreement? -

-
+ Do you require a data and information sharing agreement? +

- - + -

+ class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1" + > + Yes + + +

+

-
- + + @@ -692,13 +700,13 @@ exports[`Proprietary Data Form renders correctly when errors exist when survey d class="MuiFormControl-root" >

Is the data captured in this survey proprietary? - + + +

+ +

+ error on proprietary data category field +

+ - + +
+ + +
+

+ error on proprietor name field +

+ -

- error on proprietary data category field -

- - - -
-
- -
- - + +
+ + +
+

+ error on category rationale field +

+
+
-

- error on proprietor name field -

- - -
+
- -
- - -
-

- error on category rationale field -

-
-
-
-
- - Data and Information Sharing Agreement (DISA) - -

- Do you require a data and information sharing agreement? -

-
+ Do you require a data and information sharing agreement? +

- - + +

- Yes - - -

- error on data sharing agreement required field -

+ error on data sharing agreement required field +

+
-
- + + diff --git a/app/src/features/surveys/components/__snapshots__/StudyAreaForm.test.tsx.snap b/app/src/features/surveys/components/__snapshots__/StudyAreaForm.test.tsx.snap index 4388442ea7..84d80671dd 100644 --- a/app/src/features/surveys/components/__snapshots__/StudyAreaForm.test.tsx.snap +++ b/app/src/features/surveys/components/__snapshots__/StudyAreaForm.test.tsx.snap @@ -54,354 +54,349 @@ exports[`Study Area Form renders correctly the empty component correctly 1`] = `
-

Study Area Boundary + +

+ Import or select a boundary from existing map layers. To select an existing boundary, choose a map layer below and click a boundary on the map.

-

- Define your boundary by selecting a boundary from an existing layer or by uploading KML file or shapefile. -

-

- To select a boundary from an existing layer, select a layer from the dropdown, click a boundary on the map and click 'Add Boundary'. -

-
-
-
- -
+ +
-
-
- - ​ - -
- - - +
+ + + +
+
-
- +
+ +
+
+
+
+
+
-
- -
-
@@ -416,7 +411,7 @@ exports[`Study Area Form renders correctly the filled component correctly 1`] =