diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 15b807c2e444e16c423b3784f4ef1ff0338b8a15..66e872cf6a13633c5ff316860ac745b2397c3e79 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -12,19 +12,39 @@ build: image: docker:18.09 services: - docker:18.09-dind + only: + - dev + - rec + - master stage: build script: - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - docker build --pull -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG" . - docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG" +deploy_dev: + stage: deploy + tags: + - deploy + only: + - dev + script: + - cd /home/mps/ram + - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY + - docker-compose pull service-ram + - docker-compose up -d service-ram + - docker system prune -a -f + code_analysis: - image: skilldlabs/sonar-scanner:3.4.0 + image: skilldlabs/sonar-scanner:4.0.0 services: - docker:18.09-dind stage: sonar-analysis only: - dev + before_script: + - export NODE_PATH=$NODE_PATH:`npm root -g` + - npm install -g typescript script: - > sonar-scanner @@ -35,3 +55,14 @@ code_analysis: -Dsonar.host.url=${SONAR_URL} -Dsonar.projectKey=${SONAR_PROJECT_KEY} -Dsonar.login=${SONAR_TOKEN} + +mr: + image: docker:18.09 + services: + - docker:18.09-dind + stage: build + only: + - merge_requests + script: + - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY + - docker build . diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..048c698cd35a989e57e1bc5f3d70f9c53ff5b298 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,112 @@ +# Changelog + +All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. + +## [1.4.0](https://forge.grandlyon.com///compare/v1.3.0...v1.4.0) (2021-02-01) + + +### Features + +* **structures:** add admin delete ([43eb15b](https://forge.grandlyon.com///commit/43eb15b70acfddfb251e02f98abd0840d7eb4567)) +* add address search for structure registration ([c4811fb](https://forge.grandlyon.com///commit/c4811fb22dfab9149d94e4b87dcb79570a895f26)) +* add APTIC api structure + cron job ([88bc8fd](https://forge.grandlyon.com///commit/88bc8fdc489059fe11fb1ee83b5b5716d9daf7e0)) +* add APTIC api structure + cron job ([6605486](https://forge.grandlyon.com///commit/66054863ffda381a5f15d9419e3577717b2c9798)) +* add email sending for outdated structures ([20fd741](https://forge.grandlyon.com///commit/20fd741e6c3c9a06ebce3f056f107d28e9cf50f8)) + + +### Bug Fixes + +* add trim for opendata request to solve 400 request issue ([2d3b357](https://forge.grandlyon.com///commit/2d3b357965e0a5fa18d0e9207174a9143b1cb9cc)) +* configuration logs and bug fix for user registration ([eb8b243](https://forge.grandlyon.com///commit/eb8b24330f0380eecb1cad762c18f1cd8f6ce1b6)) +* cron duration ([2f86c0b](https://forge.grandlyon.com///commit/2f86c0bcbf6b52f83ca9f7f5de8a740bda5d135f)) +* fix duplication bug from aptic structure ([4838342](https://forge.grandlyon.com///commit/48383420588f4b221dbf920f65e30e8da9def88f)) +* merge conflit ([6a4290d](https://forge.grandlyon.com///commit/6a4290df9a0c42134f10b61c7de31d542acfc3fe)) +* outdated structure range ([0d2666f](https://forge.grandlyon.com///commit/0d2666fd7ae0922cbf188ee3fd16a570e9192195)) +* update conf ([e7070d1](https://forge.grandlyon.com///commit/e7070d130edbf875b8851eec89a4cf75ca8b3689)) +* update mailing templates, user model wrong type and aptic structure handling when a structure is updated. ([f1b69ba](https://forge.grandlyon.com///commit/f1b69ba24d961c031b4f54eeaf1cc5b0cbd0a1d8)) +* **structure:** remove unecessary sort for search ([2982dfe](https://forge.grandlyon.com///commit/2982dfe3ffa3b15f41f2bea497f16c255063e2c7)) +* **structure:** structure id creation ([7b14c80](https://forge.grandlyon.com///commit/7b14c808b8e04a34552e58eb243906b6acd9bbfc)) +* unecessary id declaration in model structure ([e788e55](https://forge.grandlyon.com///commit/e788e55e0c63155c5994b39cbc2f37871f2b4398)) + +## [1.3.0](https://forge.grandlyon.com///compare/v1.2.0...v1.3.0) (2021-01-15) + + +### Features + +* add admin module + add validation for claiming structures ([95ed7ec](https://forge.grandlyon.com///commit/95ed7eca7c099e24aeeb6ffb535e5b59858162fb)) +* add new field in structure schema ([b09c65d](https://forge.grandlyon.com///commit/b09c65d4139976d22d20589d0b3f6e62ef996e2f)) +* add role guard ([acb79f2](https://forge.grandlyon.com///commit/acb79f25c7a4c7ee447e4409be25682152b3f739)) +* add role to jwt ([f446fd9](https://forge.grandlyon.com///commit/f446fd990de074512e1dc8dcfd7bba31e8b0c9e1)) +* add sending of email when structure is validate or not by an admin ([6d683b5](https://forge.grandlyon.com///commit/6d683b5f28489c955b9dc9cfb3cf6522b07ef979)) +* add structure name in claim validation mail ([3ad4b8c](https://forge.grandlyon.com///commit/3ad4b8cc7c6b111c45814b738184e56350053da1)) +* add tcl module ([b3807e2](https://forge.grandlyon.com///commit/b3807e264a53be1717d316cd512f09bb371e454d)) +* claim structure ([8f1bc01](https://forge.grandlyon.com///commit/8f1bc01010e3575a5fd53027be7bb9e680e8fc7c)) + + +### Bug Fixes + +* mail issue after sen change ([29cdc20](https://forge.grandlyon.com///commit/29cdc20d5e636548665a4f7868dcd2802fe0a4c4)) +* structure claim check with bdd ([e0ff4df](https://forge.grandlyon.com///commit/e0ff4df5be560a3e79a563a80b824be80ce62781)) + +## [1.2.0](https://forge.grandlyon.com///compare/v1.1.0...v1.2.0) (2020-12-18) + + +### Features + +* add comments and TU ([5c0dd46](https://forge.grandlyon.com///commit/5c0dd468d34f11c9e9fea2817a3600267f043043)) +* add password cahnge endpoint ([cead66e](https://forge.grandlyon.com///commit/cead66ea129de7d7b38e026e6ee89d6fa2892f5e)) +* add password reset ([ffea481](https://forge.grandlyon.com///commit/ffea481f8275c93748c114488c150d8b9ace3edd)) +* add tu for password change ([c3eb184](https://forge.grandlyon.com///commit/c3eb184b73169d0c7611298f7e3fb00bde72a21a)) +* structure edit + data refacto ([a2a65e8](https://forge.grandlyon.com///commit/a2a65e8a2ed3fb2bb8ea1066945d7b044eee7357)) +* update dto definition for swagger ([d7a30ed](https://forge.grandlyon.com///commit/d7a30ed7a0e7e0f74e85dace474152f9a184bfc5)) +* **auth:** add expiration date on token ([771bd91](https://forge.grandlyon.com///commit/771bd9165b700eb29cc473723d363997695d7f5f)) +* **auth:** add user verification endpoint ([7b1fe8a](https://forge.grandlyon.com///commit/7b1fe8a6db800f0182c372e09c720d17771382a1)) +* **auth:** send validation mail ([ccbfe8f](https://forge.grandlyon.com///commit/ccbfe8ffda6db3f55b1ff6263f189ef583df3a61)) +* **auth:** update password strength verification, increase security with salt in env variable ([8cf88c0](https://forge.grandlyon.com///commit/8cf88c0eb37975172de43a869e30ae125edcacf3)) +* **cicd:** add sonar conf + deploy ([1595281](https://forge.grandlyon.com///commit/1595281e0254bbbc06e89cc0058a0dc58a0e9a16)) +* **cicd:** init cicd with build ([7156660](https://forge.grandlyon.com///commit/71566600376fcc43a9aa4b0486853746a6cf31be)) +* **config:** update logging and add envconfiguration handling ([2ccc5e8](https://forge.grandlyon.com///commit/2ccc5e86bc34a5afaef901ca3ae1dca80c6294f4)) +* **mailer:** add ejs template handling ([2b68810](https://forge.grandlyon.com///commit/2b68810cd5a7915da4370e91ae866020e5482df6)) + + +### Bug Fixes + +* **mailer:** update mailer service with new sen api request form ([edd8d04](https://forge.grandlyon.com///commit/edd8d0415224a5f55ef594bf7bd993d0af414b75)) +* change token variables ([64d5fd4](https://forge.grandlyon.com///commit/64d5fd4830bfcb55f8226c4184f0851fa6ca7b5b)) +* import typo ([cead291](https://forge.grandlyon.com///commit/cead291d4845201d2ebf42ef39ebda9b8299066f)) +* route in reset-password mail template ([b9de8c9](https://forge.grandlyon.com///commit/b9de8c935cd2f7ae267292d7df3b560410012127)) +* update missing import ([e9c77cb](https://forge.grandlyon.com///commit/e9c77cb64213929ad9a7cf402f00ae59f4a42260)) +* **cicd:** add mr validation build + sonar ts ([da070d2](https://forge.grandlyon.com///commit/da070d2aa8a56f70279ff641090ad0df43314abc)) +* **cicd:** add mr validation build + sonar ts ([8025ab2](https://forge.grandlyon.com///commit/8025ab2f3abed73f2354bb5254982dc0fb08628e)) +* **cicd:** docker build issue ([7732250](https://forge.grandlyon.com///commit/7732250a37b8c1edc9af9509037aebf4f232f466)) +* **cicd:** docker-compose version ([0187808](https://forge.grandlyon.com///commit/018780838acc590f5d799828a9a57a689b06f723)) +* **cicd:** image build ([78c33c2](https://forge.grandlyon.com///commit/78c33c237b194c5c86e7bdd91f52be221fb63f76)) +* **cicd:** image build ([50c1fd1](https://forge.grandlyon.com///commit/50c1fd1108fe50b9e19ebe256753a2284f87a021)) +* **TU:** Add unitary testing for auth service ([af44ba0](https://forge.grandlyon.com///commit/af44ba026e4bba564f87ad268ea2bd8ef5a5c566)) +* **TU:** fix import issues on TU and add user.service TU ([a0f9456](https://forge.grandlyon.com///commit/a0f94564ef8b04fa10503ec789901e758975888c)) +* **TU:** wrong test description ([647da39](https://forge.grandlyon.com///commit/647da394bef7a16febf223220b581f78100b685a)) +* build issue because of typo in imports ([fca81cb](https://forge.grandlyon.com///commit/fca81cb4687e141bf311d15e42c6a67e2de5e407)) + +## 1.1.0 (2020-12-01) + + +### Features + +* add categories endpoint ([b610803](https://forge.grandlyon.com///commit/b6108038843cdaf5099beb369bdd23e98d10f126)) +* add count handling ([d918fb7](https://forge.grandlyon.com///commit/d918fb7e8a15061f482359b6e9b4cf8b1e424cea)) +* add first working version for structure endpoint ([42b760b](https://forge.grandlyon.com///commit/42b760bc70029b3b19439b34aac38448ed7c65f5)) +* add full text search ([609b51b](https://forge.grandlyon.com///commit/609b51b8a40056be18cc2b7fe39674c1db463c48)) +* add health check ([0673e49](https://forge.grandlyon.com///commit/0673e49a7a9792e60910e1887ad4dcc56d453750)) +* add registration and auth ([99b3c50](https://forge.grandlyon.com///commit/99b3c509f27e7fa2d105431347658957a017dcf0)) +* first working version of auth ([8d8ff61](https://forge.grandlyon.com///commit/8d8ff617ea3065b54145502e7e8277c97304ac30)) +* parse boolean string to boolean for search filter + Refacto ([1184295](https://forge.grandlyon.com///commit/1184295c0cadf04771fc377b0b6fd09507ceecc4)) +* update readme + make coord and address call in backend instead of front ([87ca741](https://forge.grandlyon.com///commit/87ca7411ca6a61c5e23319fa472f8dff10627c8c)) +* **docker:** add docker handling for mongo database ([d66af3c](https://forge.grandlyon.com///commit/d66af3c04547e85b222ef73eeee090520edd6d22)) + + +### Bug Fixes + +* or instead of and for filter search query ([e135dfb](https://forge.grandlyon.com///commit/e135dfb02db44621f25e08d36bc8475393e62573)) +* search issue ([13e5b81](https://forge.grandlyon.com///commit/13e5b816236236ce2bf73a7179f0ea5b51ae6703)) +* typo in structure.controller ([d9000b4](https://forge.grandlyon.com///commit/d9000b4d9d98454b8f99148cd8a5e0ed1c32adee)) +* update docker-compose ([857a2d5](https://forge.grandlyon.com///commit/857a2d54ca8284778a7963828cfe6a55fcfce1ec)) diff --git a/Dockerfile b/Dockerfile index 4be220d9b7a93b6db5f3c6977b3e878973c70bf3..51ed21cd9c0c2833d924b9b89b3e582b058e5985 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,11 +7,17 @@ WORKDIR /app # A wildcard is used to ensure both package.json AND package-lock.json are copied COPY package*.json ./ -RUN npm install +RUN chgrp -R 0 /app && chmod -R g+rwX /app + +RUN npm install --silent # Bundle app source -COPY . . +COPY tsconfig.build.json . +COPY tsconfig.json . +COPY src src + +RUN npm run build -CMD ["sh","-c", "npm run start:prod"] +CMD npm run start:prod EXPOSE 3000 diff --git a/docker-compose.yml b/docker-compose.yml index fa61ea377968574c9f4a0dd047e17f8829af7e18..6c7ead9599e89777d27cae37032fb9b2a8b227fc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,4 @@ -version: '3.1' +version: '2' services: service-ram: @@ -42,5 +42,29 @@ services: ME_CONFIG_BASICAUTH_PASSWORD: ${ME_CONFIG_BASICAUTH_PASSWORD} ME_CONFIG_MONGODB_SERVER: database-ram + ghost: + image: ghost:latest + restart: always + ports: + - ${GHOST_PORT}:2368 + environment: + # see https://docs.ghost.org/docs/config#section-running-ghost-with-config-env-variables + database__client: mysql + database__connection__host: ghost-db + database__connection__user: root + database__connection__password: ${GHOST_DB_PASSWORD} + database__connection__database: ghost + # this url value is just an example, and is likely wrong for your environment! + url: http://localhost:${GHOST_PORT} + + ghost-db: + image: mysql:5.7 + restart: always + environment: + MYSQL_ROOT_PASSWORD: ${GHOST_DB_PASSWORD} + volumes: + - db-ghost + volumes: db-ram: + db-ghost: diff --git a/package-lock.json b/package-lock.json index 9942f6408c8eca009f5c9c5a533a73ffdd7b20c0..9548a30e3c319f89eff48e94dcceb9edc9792677 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "ram_server", - "version": "1.0.0", + "version": "1.4.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -233,7 +233,6 @@ "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, "requires": { "@babel/highlight": "^7.10.4" } @@ -422,8 +421,7 @@ "@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 + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==" }, "@babel/helpers": { "version": "7.12.5", @@ -440,7 +438,6 @@ "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, "requires": { "@babel/helper-validator-identifier": "^7.10.4", "chalk": "^2.0.0", @@ -1361,6 +1358,15 @@ "uuid": "8.3.1" } }, + "@nestjs/jwt": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-7.2.0.tgz", + "integrity": "sha512-uOTqYmWNpu+oS/MrdYjrWXtKGV4HkCYmAEVEFPP/KfiP/7K6fNy+boLllE6cnqESAXh9u0CLa1noAAavs+LHEQ==", + "requires": { + "@types/jsonwebtoken": "8.5.0", + "jsonwebtoken": "8.5.1" + } + }, "@nestjs/mapped-types": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-0.1.1.tgz", @@ -1371,6 +1377,11 @@ "resolved": "https://registry.npmjs.org/@nestjs/mongoose/-/mongoose-7.1.0.tgz", "integrity": "sha512-cIUgz8Gg2o827S33GtsKIOCxQDA166BqY89CjPRCOtLI72+0JzIXOca05Ms80suRQNoJxcYJ6oOUZfUni9/apA==" }, + "@nestjs/passport": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/@nestjs/passport/-/passport-7.1.5.tgz", + "integrity": "sha512-Hu9hPxTdBZA0C4GrWTsSflzwsJ99oAk9jqAwpcszdFNqfjMjkPGuCM9QsVZbBP2bE8fxrVrPsNOILS6puY8e/A==" + }, "@nestjs/platform-express": { "version": "7.5.1", "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-7.5.1.tgz", @@ -1383,6 +1394,22 @@ "tslib": "2.0.3" } }, + "@nestjs/schedule": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-0.4.1.tgz", + "integrity": "sha512-pj+zo3DJnoyGQKGguyLn9Nv1KEHZO2vNNGhtrZCIn74GsJL+CkDnd+fpgV85mypaJzjjGRogbMvXUW2UFnJAfg==", + "requires": { + "cron": "1.7.2", + "uuid": "8.3.0" + }, + "dependencies": { + "uuid": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.0.tgz", + "integrity": "sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ==" + } + } + }, "@nestjs/schematics": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-7.1.3.tgz", @@ -1647,6 +1674,11 @@ "@babel/types": "^7.3.0" } }, + "@types/bcrypt": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-3.0.0.tgz", + "integrity": "sha512-nohgNyv+1ViVcubKBh0+XiNJ3dO8nYu///9aJ4cgSqv70gBL+94SNy/iC2NLzKPT2Zt/QavrOkBVbZRLZmw6NQ==" + }, "@types/body-parser": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", @@ -1759,12 +1791,25 @@ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, + "@types/jsonwebtoken": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.0.tgz", + "integrity": "sha512-9bVao7LvyorRGZCw0VmH/dr7Og+NdjYSsKAxB43OQoComFbBgsEpoR9JW6+qSq/ogwVBg8GI2MfAlk4SYI4OLg==", + "requires": { + "@types/node": "*" + } + }, "@types/mime": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz", "integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==", "dev": true }, + "@types/minimist": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.1.tgz", + "integrity": "sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==" + }, "@types/mongodb": { "version": "3.5.34", "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.5.34.tgz", @@ -1788,14 +1833,12 @@ "@types/node": { "version": "14.14.6", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.6.tgz", - "integrity": "sha512-6QlRuqsQ/Ox/aJEQWBEJG7A9+u7oSYl3mem/K8IzxXG/kAGbV1YPD9Bg9Zw3vyxC/YP+zONKwy8hGkSt1jxFMw==", - "dev": true + "integrity": "sha512-6QlRuqsQ/Ox/aJEQWBEJG7A9+u7oSYl3mem/K8IzxXG/kAGbV1YPD9Bg9Zw3vyxC/YP+zONKwy8hGkSt1jxFMw==" }, "@types/normalize-package-data": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", - "dev": true + "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==" }, "@types/parse-json": { "version": "4.0.0", @@ -1803,6 +1846,36 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/passport": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.4.tgz", + "integrity": "sha512-h5OfAbfBBYSzjeU0GTuuqYEk9McTgWeGQql9g3gUw2/NNCfD7VgExVRYJVVeU13Twn202Mvk9BT0bUrl30sEgA==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/passport-local": { + "version": "1.0.33", + "resolved": "https://registry.npmjs.org/@types/passport-local/-/passport-local-1.0.33.tgz", + "integrity": "sha512-+rn6ZIxje0jZ2+DAiWFI8vGG7ZFKB0hXx2cUdMmudSWsigSq6ES7Emso46r4HJk0qCgrZVfI8sJiM7HIYf4SbA==", + "dev": true, + "requires": { + "@types/express": "*", + "@types/passport": "*", + "@types/passport-strategy": "*" + } + }, + "@types/passport-strategy": { + "version": "0.2.35", + "resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.35.tgz", + "integrity": "sha512-o5D19Jy2XPFoX2rKApykY15et3Apgax00RRLf0RUotPDUsYrQa7x4howLYr9El2mlUApHmCMv5CZ1IXqKFQ2+g==", + "dev": true, + "requires": { + "@types/express": "*", + "@types/passport": "*" + } + }, "@types/prettier": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.1.5.tgz", @@ -1885,6 +1958,11 @@ } } }, + "@types/validator": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.0.0.tgz", + "integrity": "sha512-WAy5txG7aFX8Vw3sloEKp5p/t/Xt8jD3GRD9DacnFv6Vo8ubudAsRTXgxpQwU0mpzY/H8U4db3roDuCMjShBmw==" + }, "@types/webpack": { "version": "4.41.21", "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.21.tgz", @@ -2266,6 +2344,15 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true }, + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, "abab": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", @@ -2275,8 +2362,7 @@ "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 + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "accepts": { "version": "1.3.7", @@ -2323,6 +2409,11 @@ "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", "dev": true }, + "add-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/add-stream/-/add-stream-1.0.0.tgz", + "integrity": "sha1-anmQQ3ynNtXhKI25K9MmbV9csqo=" + }, "ajv": { "version": "6.12.3", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", @@ -2414,8 +2505,7 @@ "ansi-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" }, "ansi-styles": { "version": "3.2.1", @@ -2443,8 +2533,45 @@ "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } }, "arg": { "version": "4.1.3", @@ -2479,11 +2606,21 @@ "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", "dev": true }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=" + }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, + "array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=" + }, "array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -2496,6 +2633,11 @@ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", "dev": true }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" + }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -2570,6 +2712,11 @@ "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", "dev": true }, + "async": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=" + }, "async-each": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", @@ -2580,8 +2727,7 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "at-least-node": { "version": "1.0.0", @@ -2803,6 +2949,15 @@ "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", "dev": true }, + "bcrypt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.0.0.tgz", + "integrity": "sha512-jB0yCBl4W/kVHM2whjfyqnxTmOHkCX4kHEa5nYKSoGeYe8YrjTYTc87/6bwt1g8cmV0QrbhKriETg9jWtcREhg==", + "requires": { + "node-addon-api": "^3.0.0", + "node-pre-gyp": "0.15.0" + } + }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -3161,6 +3316,11 @@ } } }, + "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=" + }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -3284,8 +3444,17 @@ "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, + "camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "requires": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + } }, "capture-exit": { "version": "2.0.0", @@ -3343,8 +3512,7 @@ "chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" }, "chrome-trace-event": { "version": "1.0.2", @@ -3385,6 +3553,11 @@ "integrity": "sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw==", "dev": true }, + "class-transformer": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.3.1.tgz", + "integrity": "sha512-cKFwohpJbuMovS8xVLmn8N2AUbAuc8pVo4zEfsUVo8qgECOogns1WVk/FkOZoxhOPTyTYFckuoH+13FO+MQ8GA==" + }, "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", @@ -3408,6 +3581,17 @@ } } }, + "class-validator": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.12.2.tgz", + "integrity": "sha512-TDzPzp8BmpsbPhQpccB3jMUE/3pK0TyqamrK0kcx+ZeFytMA+O6q87JZZGObHHnoo9GM8vl/JppIyKWeEA/EVw==", + "requires": { + "@types/validator": "13.0.0", + "google-libphonenumber": "^3.2.8", + "tslib": ">=1.9.0", + "validator": "13.0.0" + } + }, "cli-boxes": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", @@ -3483,7 +3667,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -3511,6 +3694,11 @@ "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", "dev": true }, + "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=" + }, "collect-v8-coverage": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", @@ -3551,7 +3739,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "requires": { "delayed-stream": "~1.0.0" } @@ -3568,6 +3755,15 @@ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, + "compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "requires": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + }, "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", @@ -3661,6 +3857,11 @@ "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", "dev": true }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, "constants-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", @@ -3680,159 +3881,656 @@ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" }, - "convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" - }, - "cookiejar": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", - "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", - "dev": true - }, - "copy-concurrently": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", - "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", - "dev": true, + "conventional-changelog": { + "version": "3.1.23", + "resolved": "https://registry.npmjs.org/conventional-changelog/-/conventional-changelog-3.1.23.tgz", + "integrity": "sha512-sScUu2NHusjRC1dPc5p8/b3kT78OYr95/Bx7Vl8CPB8tF2mG1xei5iylDTRjONV5hTlzt+Cn/tBWrKdd299b7A==", "requires": { - "aproba": "^1.1.1", - "fs-write-stream-atomic": "^1.0.8", - "iferr": "^0.1.5", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.0" - }, - "dependencies": { - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } + "conventional-changelog-angular": "^5.0.11", + "conventional-changelog-atom": "^2.0.7", + "conventional-changelog-codemirror": "^2.0.7", + "conventional-changelog-conventionalcommits": "^4.4.0", + "conventional-changelog-core": "^4.2.0", + "conventional-changelog-ember": "^2.0.8", + "conventional-changelog-eslint": "^3.0.8", + "conventional-changelog-express": "^2.0.5", + "conventional-changelog-jquery": "^3.0.10", + "conventional-changelog-jshint": "^2.0.8", + "conventional-changelog-preset-loader": "^2.3.4" } }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true - }, - "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=" - }, - "cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "conventional-changelog-angular": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.12.tgz", + "integrity": "sha512-5GLsbnkR/7A89RyHLvvoExbiGbd9xKdKqDTrArnPbOqBqG/2wIosu0fHwpeIRI8Tl94MhVNBXcLJZl92ZQ5USw==", "requires": { - "object-assign": "^4", - "vary": "^1" + "compare-func": "^2.0.0", + "q": "^1.5.1" } }, - "cosmiconfig": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", - "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", - "dev": true, + "conventional-changelog-atom": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/conventional-changelog-atom/-/conventional-changelog-atom-2.0.8.tgz", + "integrity": "sha512-xo6v46icsFTK3bb7dY/8m2qvc8sZemRgdqLb/bjpBsH2UyOS8rKNTgcb5025Hri6IpANPApbXMg15QLb1LJpBw==", "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.7.2" + "q": "^1.5.1" } }, - "create-ecdh": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", - "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", - "dev": true, + "conventional-changelog-codemirror": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/conventional-changelog-codemirror/-/conventional-changelog-codemirror-2.0.8.tgz", + "integrity": "sha512-z5DAsn3uj1Vfp7po3gpt2Boc+Bdwmw2++ZHa5Ak9k0UKsYAO5mH1UBTN0qSCuJZREIhX6WU4E1p3IW2oRCNzQw==", "requires": { - "bn.js": "^4.1.0", - "elliptic": "^6.5.3" - }, - "dependencies": { - "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", - "dev": true - } + "q": "^1.5.1" } }, - "create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dev": true, - "requires": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } + "conventional-changelog-config-spec": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-config-spec/-/conventional-changelog-config-spec-2.1.0.tgz", + "integrity": "sha512-IpVePh16EbbB02V+UA+HQnnPIohgXvJRxHcS5+Uwk4AT5LjzCZJm5sp/yqs5C6KZJ1jMsV4paEV13BN1pvDuxQ==" }, - "create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dev": true, + "conventional-changelog-conventionalcommits": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-4.4.0.tgz", + "integrity": "sha512-ybvx76jTh08tpaYrYn/yd0uJNLt5yMrb1BphDe4WBredMlvPisvMghfpnJb6RmRNcqXeuhR6LfGZGewbkRm9yA==", "requires": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" + "compare-func": "^2.0.0", + "lodash": "^4.17.15", + "q": "^1.5.1" } }, - "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, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "conventional-changelog-core": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-4.2.1.tgz", + "integrity": "sha512-8cH8/DEoD3e5Q6aeogdR5oaaKs0+mG6+f+Om0ZYt3PNv7Zo0sQhu4bMDRsqAF+UTekTAtP1W/C41jH/fkm8Jtw==", + "requires": { + "add-stream": "^1.0.0", + "conventional-changelog-writer": "^4.0.18", + "conventional-commits-parser": "^3.2.0", + "dateformat": "^3.0.0", + "get-pkg-repo": "^1.0.0", + "git-raw-commits": "2.0.0", + "git-remote-origin-url": "^2.0.0", + "git-semver-tags": "^4.1.1", + "lodash": "^4.17.15", + "normalize-package-data": "^3.0.0", + "q": "^1.5.1", + "read-pkg": "^3.0.0", + "read-pkg-up": "^3.0.0", + "shelljs": "^0.8.3", + "through2": "^4.0.0" }, "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "requires": { + "locate-path": "^2.0.0" + } + }, + "hosted-git-info": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.7.tgz", + "integrity": "sha512-fWqc0IcuXs+BmE9orLDyVykAG9GJtGLGuZAAqgcckPgv5xad4AcXGIv8galtQvlwutxSlaMcdw7BUtq2EIvqCQ==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "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=", + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "normalize-package-data": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.0.tgz", + "integrity": "sha512-6lUjEI0d3v6kFrtgA/lOx4zHCWULXsFNIjHolnZCKCTLA6m/G625cdn3O7eNmT0iD3jfo6HZ9cdImGZwf21prw==", + "requires": { + "hosted-git-info": "^3.0.6", + "resolve": "^1.17.0", + "semver": "^7.3.2", + "validate-npm-package-license": "^3.0.1" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "dependencies": { + "hosted-git-info": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==" + }, + "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==", + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, + "read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "requires": { + "readable-stream": "3" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, + "conventional-changelog-ember": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/conventional-changelog-ember/-/conventional-changelog-ember-2.0.9.tgz", + "integrity": "sha512-ulzIReoZEvZCBDhcNYfDIsLTHzYHc7awh+eI44ZtV5cx6LVxLlVtEmcO+2/kGIHGtw+qVabJYjdI5cJOQgXh1A==", + "requires": { + "q": "^1.5.1" + } + }, + "conventional-changelog-eslint": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/conventional-changelog-eslint/-/conventional-changelog-eslint-3.0.9.tgz", + "integrity": "sha512-6NpUCMgU8qmWmyAMSZO5NrRd7rTgErjrm4VASam2u5jrZS0n38V7Y9CzTtLT2qwz5xEChDR4BduoWIr8TfwvXA==", + "requires": { + "q": "^1.5.1" + } + }, + "conventional-changelog-express": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/conventional-changelog-express/-/conventional-changelog-express-2.0.6.tgz", + "integrity": "sha512-SDez2f3iVJw6V563O3pRtNwXtQaSmEfTCaTBPCqn0oG0mfkq0rX4hHBq5P7De2MncoRixrALj3u3oQsNK+Q0pQ==", + "requires": { + "q": "^1.5.1" + } + }, + "conventional-changelog-jquery": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/conventional-changelog-jquery/-/conventional-changelog-jquery-3.0.11.tgz", + "integrity": "sha512-x8AWz5/Td55F7+o/9LQ6cQIPwrCjfJQ5Zmfqi8thwUEKHstEn4kTIofXub7plf1xvFA2TqhZlq7fy5OmV6BOMw==", + "requires": { + "q": "^1.5.1" + } + }, + "conventional-changelog-jshint": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/conventional-changelog-jshint/-/conventional-changelog-jshint-2.0.9.tgz", + "integrity": "sha512-wMLdaIzq6TNnMHMy31hql02OEQ8nCQfExw1SE0hYL5KvU+JCTuPaDO+7JiogGT2gJAxiUGATdtYYfh+nT+6riA==", + "requires": { + "compare-func": "^2.0.0", + "q": "^1.5.1" + } + }, + "conventional-changelog-preset-loader": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.3.4.tgz", + "integrity": "sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g==" + }, + "conventional-changelog-writer": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.0.18.tgz", + "integrity": "sha512-mAQDCKyB9HsE8Ko5cCM1Jn1AWxXPYV0v8dFPabZRkvsiWUul2YyAqbIaoMKF88Zf2ffnOPSvKhboLf3fnjo5/A==", + "requires": { + "compare-func": "^2.0.0", + "conventional-commits-filter": "^2.0.7", + "dateformat": "^3.0.0", + "handlebars": "^4.7.6", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.15", + "meow": "^8.0.0", + "semver": "^6.0.0", + "split": "^1.0.0", + "through2": "^4.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + }, + "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" + } + }, + "through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "requires": { + "readable-stream": "3" + } + } + } + }, + "conventional-commits-filter": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.7.tgz", + "integrity": "sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA==", + "requires": { + "lodash.ismatch": "^4.4.0", + "modify-values": "^1.0.0" + } + }, + "conventional-commits-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.2.0.tgz", + "integrity": "sha512-XmJiXPxsF0JhAKyfA2Nn+rZwYKJ60nanlbSWwwkGwLQFbugsc0gv1rzc7VbbUWAzJfR1qR87/pNgv9NgmxtBMQ==", + "requires": { + "JSONStream": "^1.0.4", + "is-text-path": "^1.0.1", + "lodash": "^4.17.15", + "meow": "^8.0.0", + "split2": "^2.0.0", + "through2": "^4.0.0", + "trim-off-newlines": "^1.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "requires": { + "readable-stream": "3" + } + } + } + }, + "conventional-recommended-bump": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-6.0.10.tgz", + "integrity": "sha512-2ibrqAFMN3ZA369JgVoSbajdD/BHN6zjY7DZFKTHzyzuQejDUCjQ85S5KHxCRxNwsbDJhTPD5hOKcis/jQhRgg==", + "requires": { + "concat-stream": "^2.0.0", + "conventional-changelog-preset-loader": "^2.3.4", + "conventional-commits-filter": "^2.0.6", + "conventional-commits-parser": "^3.1.0", + "git-raw-commits": "2.0.0", + "git-semver-tags": "^4.1.0", + "meow": "^7.0.0", + "q": "^1.5.1" + }, + "dependencies": { + "concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "meow": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-7.1.1.tgz", + "integrity": "sha512-GWHvA5QOcS412WCo8vwKDlTelGLsCGBVevQB5Kva961rmNfun0PCbv5+xta2kUMFJyR8/oWnn7ddeKdosbAPbA==", + "requires": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^2.5.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.13.1", + "yargs-parser": "^18.1.3" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==" + } + } + }, + "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, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", + "dev": true + }, + "copy-concurrently": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", + "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", + "dev": true, + "requires": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + }, + "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "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=" + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + } + }, + "create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "cron": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/cron/-/cron-1.7.2.tgz", + "integrity": "sha512-+SaJ2OfeRvfQqwXQ2kgr0Y5pzBR/lijf5OpnnaruwWnmI799JfWr2jN2ItOV9s3A/+TFOt6mxvKzQq5F0Jp6VQ==", + "requires": { + "moment-timezone": "^0.5.x" + } + }, + "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, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", "dev": true, "requires": { "browserify-cipher": "^1.0.0", @@ -3877,12 +4575,28 @@ } } }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "requires": { + "array-find-index": "^1.0.1" + } + }, "cyclist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", "dev": true }, + "dargs": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/dargs/-/dargs-4.1.0.tgz", + "integrity": "sha1-A6nbtLXC8Tm/FK5T8LiipqhvThc=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -3903,6 +4617,11 @@ "whatwg-url": "^8.0.0" } }, + "dateformat": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", + "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==" + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -3914,8 +4633,23 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "decamelize-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", + "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", + "requires": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "dependencies": { + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=" + } + } }, "decimal.js": { "version": "10.2.1", @@ -3941,8 +4675,7 @@ "deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" }, "deep-is": { "version": "0.1.3", @@ -4015,8 +4748,12 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, "denque": { "version": "1.4.1", @@ -4043,11 +4780,20 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, + "detect-indent": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.0.0.tgz", + "integrity": "sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA==" + }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + }, "detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==" }, "dicer": { "version": "0.2.5", @@ -4134,7 +4880,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dev": true, "requires": { "is-obj": "^2.0.0" } @@ -4144,6 +4889,15 @@ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==" }, + "dotgitignore": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/dotgitignore/-/dotgitignore-2.1.0.tgz", + "integrity": "sha512-sCm11ak2oY6DglEPpCB8TixLjWAxd3kJTs6UIcSasNYxXdFPV+YKlye92c8H4kKFqV5qYMIh7d+cYecEg0dIkA==", + "requires": { + "find-up": "^3.0.0", + "minimatch": "^3.0.4" + } + }, "duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", @@ -4204,11 +4958,27 @@ "safer-buffer": "^2.1.0" } }, + "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==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, + "ejs": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.5.tgz", + "integrity": "sha512-dldq3ZfFtgVTJMLjOe+/3sROTzALlL9E34V4/sDtUd/KlBSS0s6U1/+WPE1B4sj9CXHJpL1M6rhNJnc9Wbal9w==", + "requires": { + "jake": "^10.6.1" + } + }, "elliptic": { "version": "6.5.3", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", @@ -4241,8 +5011,7 @@ "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "emojis-list": { "version": "3.0.0", @@ -4297,7 +5066,6 @@ "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, "requires": { "is-arrayish": "^0.2.1" } @@ -5034,7 +5802,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, "requires": { "escape-string-regexp": "^1.0.5" } @@ -5055,6 +5822,14 @@ "dev": true, "optional": true }, + "filelist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.1.tgz", + "integrity": "sha512-8zSK6Nu0DQIC08mUC46sWGXi+q3GGpKydAG36k+JDba6VRpkevvOWUW5a/PhShij4+vHT9M+ghgG7eM+a9JDUQ==", + "requires": { + "minimatch": "^3.0.4" + } + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -5093,7 +5868,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, "requires": { "locate-path": "^3.0.0" } @@ -5256,13 +6030,12 @@ } }, "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", + "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", "requires": { "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", + "combined-stream": "^1.0.8", "mime-types": "^2.1.12" } }, @@ -5333,6 +6106,14 @@ } } }, + "fs-access": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", + "integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=", + "requires": { + "null-check": "^1.0.0" + } + }, "fs-extra": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", @@ -5345,6 +6126,14 @@ "universalify": "^1.0.0" } }, + "fs-minipass": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", + "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "requires": { + "minipass": "^2.6.0" + } + }, "fs-monkey": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.1.tgz", @@ -5378,8 +6167,7 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "functional-red-black-tree": { "version": "1.0.1", @@ -5387,6 +6175,54 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "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=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -5396,8 +6232,7 @@ "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, "get-package-type": { "version": "0.1.0", @@ -5405,34 +6240,388 @@ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true }, + "get-pkg-repo": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-pkg-repo/-/get-pkg-repo-1.4.0.tgz", + "integrity": "sha1-xztInAbYDMVTbCyFP54FIyBWly0=", + "requires": { + "hosted-git-info": "^2.1.4", + "meow": "^3.3.0", + "normalize-package-data": "^2.3.0", + "parse-github-repo-url": "^1.3.0", + "through2": "^2.0.0" + }, + "dependencies": { + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "requires": { + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=" + }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "requires": { + "repeating": "^2.0.0" + } + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=" + }, + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "requires": { + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "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=", + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "requires": { + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" + } + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "requires": { + "get-stdin": "^4.0.1" + } + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=" + } + } + }, "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 }, - "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, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "git-raw-commits": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.0.tgz", + "integrity": "sha512-w4jFEJFgKXMQJ0H0ikBk2S+4KP2VEjhCvLCNqbNRQC8BgGWgLKNCO7a9K9LI+TVT7Gfoloje502sEnctibffgg==", + "requires": { + "dargs": "^4.0.1", + "lodash.template": "^4.0.2", + "meow": "^4.0.0", + "split2": "^2.0.0", + "through2": "^2.0.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" + }, + "camelcase-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", + "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", + "requires": { + "camelcase": "^4.1.0", + "map-obj": "^2.0.0", + "quick-lru": "^1.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "requires": { + "locate-path": "^2.0.0" + } + }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=" + }, + "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=", + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "map-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", + "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=" + }, + "meow": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz", + "integrity": "sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A==", + "requires": { + "camelcase-keys": "^4.0.0", + "decamelize-keys": "^1.0.0", + "loud-rejection": "^1.0.0", + "minimist": "^1.1.3", + "minimist-options": "^3.0.1", + "normalize-package-data": "^2.3.4", + "read-pkg-up": "^3.0.0", + "redent": "^2.0.0", + "trim-newlines": "^2.0.0" + } + }, + "minimist-options": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", + "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", + "requires": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + }, + "quick-lru": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", + "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=" + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" + } + }, + "redent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", + "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", + "requires": { + "indent-string": "^3.0.0", + "strip-indent": "^2.0.0" + } + }, + "strip-indent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", + "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=" + }, + "trim-newlines": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", + "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=" + } + } + }, + "git-remote-origin-url": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz", + "integrity": "sha1-UoJlna4hBxRaERJhEq0yFuxfpl8=", "requires": { - "pump": "^3.0.0" + "gitconfiglocal": "^1.0.0", + "pify": "^2.3.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + } } }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true + "git-semver-tags": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-4.1.1.tgz", + "integrity": "sha512-OWyMt5zBe7xFs8vglMmhM9lRQzCWL3WjHtxNNfJTMngGym7pC1kh8sP6jevfydJ6LP3ZvGxfb6ABYgPUM0mtsA==", + "requires": { + "meow": "^8.0.0", + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, + "gitconfiglocal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz", + "integrity": "sha1-QdBF84UaXqiPA/JMocYXgRRGS5s=", "requires": { - "assert-plus": "^1.0.0" + "ini": "^1.3.2" } }, "glob": { @@ -5497,6 +6686,11 @@ "slash": "^3.0.0" } }, + "google-libphonenumber": { + "version": "3.2.15", + "resolved": "https://registry.npmjs.org/google-libphonenumber/-/google-libphonenumber-3.2.15.tgz", + "integrity": "sha512-tbCIuzMoH34RdrbFRw5kijAZn/p6JMQvsgtr1glg2ugbwqrMPlOL8pHNK8cyGo9B6SXpcMm4hdyDqwomR+HPRg==" + }, "got": { "version": "9.6.0", "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", @@ -5519,8 +6713,7 @@ "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 + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" }, "growly": { "version": "1.3.0", @@ -5529,6 +6722,25 @@ "dev": true, "optional": true }, + "handlebars": { + "version": "4.7.6", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz", + "integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==", + "requires": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4", + "wordwrap": "^1.0.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -5545,11 +6757,15 @@ "har-schema": "^2.0.0" } }, + "hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==" + }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -5559,6 +6775,11 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, "has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", @@ -5686,8 +6907,7 @@ "hosted-git-info": { "version": "2.8.8", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", - "dev": true + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==" }, "html-encoding-sniffer": { "version": "2.0.1", @@ -5777,6 +6997,14 @@ "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", "dev": true }, + "ignore-walk": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", + "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", + "requires": { + "minimatch": "^3.0.4" + } + }, "import-fresh": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.2.tgz", @@ -5854,6 +7082,11 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==" + }, "infer-owner": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", @@ -5877,8 +7110,7 @@ "ini": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "dev": true + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, "inquirer": { "version": "7.3.3", @@ -5961,8 +7193,7 @@ "interpret": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "dev": true + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==" }, "ip-regex": { "version": "2.1.0", @@ -5998,8 +7229,7 @@ "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" }, "is-binary-path": { "version": "2.1.0", @@ -6029,7 +7259,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.0.0.tgz", "integrity": "sha512-jq1AH6C8MuteOoBPwkxHafmByhL9j5q4OaPGdbuD+ZtQJVzH+i6E3BJDQcBA09k57i2Hh2yQbEG8yObZ0jdlWw==", - "dev": true, "requires": { "has": "^1.0.3" } @@ -6092,11 +7321,15 @@ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, + "is-finite": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", + "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==" + }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, "is-generator-fn": { "version": "2.1.0", @@ -6144,8 +7377,7 @@ "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 + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" }, "is-path-inside": { "version": "3.0.2", @@ -6153,6 +7385,11 @@ "integrity": "sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==", "dev": true }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" + }, "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -6174,12 +7411,25 @@ "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", "dev": true }, + "is-text-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", + "integrity": "sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4=", + "requires": { + "text-extensions": "^1.0.0" + } + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "dev": true }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" + }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -6339,6 +7589,17 @@ "resolved": "https://registry.npmjs.org/iterare/-/iterare-1.2.1.tgz", "integrity": "sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==" }, + "jake": { + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.2.tgz", + "integrity": "sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==", + "requires": { + "async": "0.9.x", + "chalk": "^2.4.2", + "filelist": "^1.0.1", + "minimatch": "^3.0.4" + } + }, "jest": { "version": "26.6.3", "resolved": "https://registry.npmjs.org/jest/-/jest-26.6.3.tgz", @@ -7636,8 +8897,7 @@ "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 + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "js-yaml": { "version": "3.14.0", @@ -7712,14 +8972,12 @@ "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 + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" }, "json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, "json-schema": { "version": "0.2.3", @@ -7742,8 +9000,7 @@ "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "json5": { "version": "1.0.1", @@ -7772,6 +9029,40 @@ } } }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" + }, + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "requires": { + "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" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -7784,6 +9075,25 @@ "verror": "1.10.0" } }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "kareem": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.1.tgz", @@ -7801,8 +9111,7 @@ "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 + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" }, "kleur": { "version": "3.0.3", @@ -7838,8 +9147,42 @@ "lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", - "dev": true + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=" + }, + "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=", + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + }, + "dependencies": { + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "requires": { + "error-ex": "^1.2.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "requires": { + "is-utf8": "^0.2.0" + } + } + } }, "loader-runner": { "version": "2.4.0", @@ -7862,7 +9205,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, "requires": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" @@ -7873,18 +9215,80 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" }, + "lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=" + }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "lodash.ismatch": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", + "integrity": "sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc=" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", "dev": true }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, "lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", "dev": true }, + "lodash.template": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", + "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", + "requires": { + "lodash._reinterpolate": "^3.0.0", + "lodash.templatesettings": "^4.0.0" + } + }, + "lodash.templatesettings": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", + "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", + "requires": { + "lodash._reinterpolate": "^3.0.0" + } + }, "lodash.toarray": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz", @@ -7900,6 +9304,15 @@ "chalk": "^2.4.2" } }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "requires": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + } + }, "lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", @@ -7915,6 +9328,11 @@ "yallist": "^3.0.2" } }, + "luxon": { + "version": "1.25.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.25.0.tgz", + "integrity": "sha512-hEgLurSH8kQRjY6i4YLey+mcKVAWXbDNlZRmM6AgWDJ1cY3atl8Ztf5wEY7VBReFbmGnwQPz7KYJblL8B2k0jQ==" + }, "macos-release": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.4.1.tgz", @@ -7969,6 +9387,11 @@ "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", "dev": true }, + "map-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz", + "integrity": "sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g==" + }, "map-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", @@ -8051,6 +9474,68 @@ "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", "optional": true }, + "meow": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-8.0.0.tgz", + "integrity": "sha512-nbsTRz2fwniJBFgUkcdISq8y/q9n9VbiHYbfwklFh5V4V2uAcxtKQkDc0yCLPM/kP0d+inZBewn3zJqewHE7kg==", + "requires": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" + }, + "dependencies": { + "hosted-git-info": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.7.tgz", + "integrity": "sha512-fWqc0IcuXs+BmE9orLDyVykAG9GJtGLGuZAAqgcckPgv5xad4AcXGIv8galtQvlwutxSlaMcdw7BUtq2EIvqCQ==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "normalize-package-data": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.0.tgz", + "integrity": "sha512-6lUjEI0d3v6kFrtgA/lOx4zHCWULXsFNIjHolnZCKCTLA6m/G625cdn3O7eNmT0iD3jfo6HZ9cdImGZwf21prw==", + "requires": { + "hosted-git-info": "^3.0.6", + "resolve": "^1.17.0", + "semver": "^7.3.2", + "validate-npm-package-license": "^3.0.1" + } + }, + "type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==" + } + } + }, "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -8226,6 +9711,11 @@ "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", "dev": true }, + "min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==" + }, "minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -8251,6 +9741,33 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, + "minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "requires": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + } + }, + "minipass": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", + "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "requires": { + "minipass": "^2.9.0" + } + }, "mississippi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", @@ -8298,6 +9815,24 @@ "minimist": "^1.2.5" } }, + "modify-values": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", + "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==" + }, + "moment": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" + }, + "moment-timezone": { + "version": "0.5.32", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.32.tgz", + "integrity": "sha512-Z8QNyuQHQAmWucp8Knmgei8YNo28aLjJq6Ma+jy1ZSpSk5nyfRT8xgUbSQvD2+2UajISfenndwvFuH3NGS+nvA==", + "requires": { + "moment": ">= 2.9.0" + } + }, "mongodb": { "version": "3.6.3", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.3.tgz", @@ -8461,6 +9996,31 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "needle": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.5.2.tgz", + "integrity": "sha512-LbRIwS9BfkPvNwNHlsA41Q29kL2L/6VaOJ0qisM5lLWsTV3nP15abO5ITL6L81zqFhzjRKDAYjpcBcwM0AVvLQ==", + "requires": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", @@ -8469,8 +10029,7 @@ "neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, "nice-try": { "version": "1.0.5", @@ -8478,6 +10037,11 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "node-addon-api": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.0.2.tgz", + "integrity": "sha512-+D4s2HCnxPd5PjjI0STKwncjXTUKKqm74MDMz9OPXavjsGmjkvwgLtA5yoxJUdmpj52+2u+RrXgPipahKczMKg==" + }, "node-emoji": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.10.0.tgz", @@ -8629,6 +10193,47 @@ } } }, + "node-pre-gyp": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.15.0.tgz", + "integrity": "sha512-7QcZa8/fpaU/BKenjcaeFF9hLz2+7S9AqyXFhlH/rilsQ/hPZKK32RtR5EQHJElgu+q5RfbJ34KriI79UWaorA==", + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.3", + "needle": "^2.5.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4.4.2" + }, + "dependencies": { + "nopt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", + "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, "nodemon": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.6.tgz", @@ -8683,7 +10288,6 @@ "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, "requires": { "hosted-git-info": "^2.1.4", "resolve": "^1.10.0", @@ -8694,8 +10298,7 @@ "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" } } }, @@ -8711,6 +10314,29 @@ "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", "dev": true }, + "npm-bundled": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", + "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==" + }, + "npm-packlist": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", + "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1", + "npm-normalize-package-bin": "^1.0.1" + } + }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", @@ -8720,6 +10346,27 @@ "path-key": "^2.0.0" } }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "null-check": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz", + "integrity": "sha1-l33/1xdgErnsMNKjnbXPcqBDnt0=" + }, + "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=" + }, "nwsapi": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", @@ -8918,6 +10565,11 @@ "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", "dev": true }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, "os-name": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/os-name/-/os-name-3.1.0.tgz", @@ -8931,8 +10583,16 @@ "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } }, "p-cancelable": { "version": "1.1.0", @@ -8956,7 +10616,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, "requires": { "p-try": "^2.0.0" } @@ -8965,7 +10624,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, "requires": { "p-limit": "^2.0.0" } @@ -8973,8 +10631,7 @@ "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, "package-json": { "version": "6.5.0", @@ -9067,11 +10724,15 @@ "safe-buffer": "^5.1.1" } }, + "parse-github-repo-url": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/parse-github-repo-url/-/parse-github-repo-url-1.4.1.tgz", + "integrity": "sha1-nn2LslKmy2ukJZUGC3v23z28H1A=" + }, "parse-json": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", - "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -9096,6 +10757,37 @@ "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", "dev": true }, + "passport": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.4.1.tgz", + "integrity": "sha512-IxXgZZs8d7uFSt3eqNjM9NQ3g3uQCW5avD8mRNoXV99Yig50vjuaez6dQK2qC0kVWPRTujxY0dWgGfT09adjYg==", + "requires": { + "passport-strategy": "1.x.x", + "pause": "0.0.1" + } + }, + "passport-jwt": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.0.tgz", + "integrity": "sha512-BwC0n2GP/1hMVjR4QpnvqA61TxenUMlmfNjYNgK0ZAs0HK4SOQkHcSv4L328blNTLtHq7DbmvyNJiH+bn6C5Mg==", + "requires": { + "jsonwebtoken": "^8.2.0", + "passport-strategy": "^1.0.0" + } + }, + "passport-local": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", + "integrity": "sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4=", + "requires": { + "passport-strategy": "1.x.x" + } + }, + "passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=" + }, "path-browserify": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", @@ -9112,8 +10804,7 @@ "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" }, "path-is-absolute": { "version": "1.0.1", @@ -9129,8 +10820,7 @@ "path-parse": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" }, "path-to-regexp": { "version": "3.2.0", @@ -9143,6 +10833,11 @@ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true }, + "pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" + }, "pbkdf2": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", @@ -9174,6 +10869,19 @@ "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "requires": { + "pinkie": "^2.0.0" + } + }, "pirates": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", @@ -9399,6 +11107,11 @@ "escape-goat": "^2.0.0" } }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" + }, "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", @@ -9416,6 +11129,11 @@ "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", "dev": true }, + "quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==" + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -9455,7 +11173,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, "requires": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -9466,8 +11183,7 @@ "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" } } }, @@ -9481,7 +11197,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, "requires": { "@types/normalize-package-data": "^2.4.0", "normalize-package-data": "^2.5.0", @@ -9492,8 +11207,7 @@ "type-fest": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==" } } }, @@ -9501,7 +11215,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, "requires": { "find-up": "^4.1.0", "read-pkg": "^5.2.0", @@ -9512,7 +11225,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, "requires": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -9522,7 +11234,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, "requires": { "p-locate": "^4.1.0" } @@ -9531,7 +11242,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, "requires": { "p-limit": "^2.2.0" } @@ -9539,14 +11249,12 @@ "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" }, "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 + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" } } }, @@ -9574,11 +11282,19 @@ "version": "0.6.2", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", - "dev": true, "requires": { "resolve": "^1.1.6" } }, + "redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "requires": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + } + }, "reflect-metadata": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", @@ -9641,6 +11357,14 @@ "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", "dev": true }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "requires": { + "is-finite": "^1.0.0" + } + }, "request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", @@ -9669,6 +11393,17 @@ "uuid": "^3.3.2" }, "dependencies": { + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", @@ -9728,14 +11463,12 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" }, "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==", - "dev": true + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" }, "require_optional": { "version": "1.0.1", @@ -9762,7 +11495,6 @@ "version": "1.18.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz", "integrity": "sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA==", - "dev": true, "requires": { "is-core-module": "^2.0.0", "path-parse": "^1.0.6" @@ -9954,6 +11686,11 @@ "sparse-bitfield": "^3.0.3" } }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, "saxes": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", @@ -9977,8 +11714,7 @@ "semver": { "version": "7.3.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", - "dev": true + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" }, "semver-diff": { "version": "3.1.1", @@ -10047,8 +11783,7 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "set-value": { "version": "2.0.1", @@ -10113,7 +11848,6 @@ "version": "0.8.4", "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz", "integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==", - "dev": true, "requires": { "glob": "^7.0.0", "interpret": "^1.0.0", @@ -10135,8 +11869,7 @@ "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 + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" }, "sisteransi": { "version": "1.0.5", @@ -10355,7 +12088,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" @@ -10364,14 +12096,12 @@ "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 + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" }, "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, "requires": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" @@ -10380,8 +12110,15 @@ "spdx-license-ids": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz", - "integrity": "sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw==", - "dev": true + "integrity": "sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw==" + }, + "split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "requires": { + "through": "2" + } }, "split-string": { "version": "3.1.0", @@ -10392,6 +12129,14 @@ "extend-shallow": "^3.0.0" } }, + "split2": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz", + "integrity": "sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==", + "requires": { + "through2": "^2.0.2" + } + }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -10441,6 +12186,60 @@ } } }, + "standard-version": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/standard-version/-/standard-version-9.0.0.tgz", + "integrity": "sha512-eRR04IscMP3xW9MJTykwz13HFNYs8jS33AGuDiBKgfo5YrO0qX0Nxb4rjupVwT5HDYL/aR+MBEVLjlmVFmFEDQ==", + "requires": { + "chalk": "^2.4.2", + "conventional-changelog": "3.1.23", + "conventional-changelog-config-spec": "2.1.0", + "conventional-changelog-conventionalcommits": "4.4.0", + "conventional-recommended-bump": "6.0.10", + "detect-indent": "^6.0.0", + "detect-newline": "^3.1.0", + "dotgitignore": "^2.1.0", + "figures": "^3.1.0", + "find-up": "^4.1.0", + "fs-access": "^1.0.1", + "git-semver-tags": "^4.0.0", + "semver": "^7.1.1", + "stringify-package": "^1.0.1", + "yargs": "^15.3.1" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "requires": { + "p-limit": "^2.2.0" + } + }, + "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==" + } + } + }, "static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", @@ -10595,7 +12394,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -10607,11 +12405,15 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" }, + "stringify-package": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stringify-package/-/stringify-package-1.0.1.tgz", + "integrity": "sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg==" + }, "strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, "requires": { "ansi-regex": "^5.0.0" } @@ -10619,8 +12421,7 @@ "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" }, "strip-eof": { "version": "1.0.0", @@ -10634,6 +12435,14 @@ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true }, + "strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "requires": { + "min-indent": "^1.0.0" + } + }, "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -10853,6 +12662,20 @@ "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", "dev": true }, + "tar": { + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", + "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + } + }, "term-size": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", @@ -10941,6 +12764,11 @@ "minimatch": "^3.0.4" } }, + "text-extensions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", + "integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==" + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -10956,14 +12784,12 @@ "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, "through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, "requires": { "readable-stream": "~2.3.6", "xtend": "~4.0.1" @@ -10972,14 +12798,12 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -10994,7 +12818,6 @@ "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, "requires": { "safe-buffer": "~5.1.0" } @@ -11124,6 +12947,16 @@ "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "dev": true }, + "trim-newlines": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.0.tgz", + "integrity": "sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA==" + }, + "trim-off-newlines": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz", + "integrity": "sha1-n5up2e+odkw4dpi8v+sshI8RrbM=" + }, "ts-jest": { "version": "26.4.3", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.4.3.tgz", @@ -11344,6 +13177,12 @@ "integrity": "sha512-ywmr/VrTVCmNTJ6iV2LwIrfG1P+lv6luD8sUJs+2eI9NLGigaN+nUQc13iHqisq7bra9lnmUSYqbJvegraBOPQ==", "dev": true }, + "uglify-js": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.12.1.tgz", + "integrity": "sha512-o8lHP20KjIiQe5b/67Rh68xEGRrc2SRsCuuoYclXXoC74AfSRGblU1HKzJWH3HxPZ+Ort85fWHpSX7KwBUC9CQ==", + "optional": true + }, "undefsafe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", @@ -11621,12 +13460,16 @@ "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, "requires": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" } }, + "validator": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.0.0.tgz", + "integrity": "sha512-anYx5fURbgF04lQV18nEQWZ/3wHGnxiKdG4aL8J+jEDsm98n/sU/bey+tYk6tnGJzm7ioh5FoqrAiQ6m03IgaA==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -12074,8 +13917,44 @@ "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "requires": { + "string-width": "^1.0.2 || 2" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "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=" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } + } }, "widest-line": { "version": "3.1.0", @@ -12101,6 +13980,11 @@ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" + }, "worker-farm": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", @@ -12114,7 +13998,6 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, "requires": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -12125,7 +14008,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "requires": { "color-convert": "^2.0.1" } @@ -12134,7 +14016,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "requires": { "color-name": "~1.1.4" } @@ -12142,8 +14023,7 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" } } }, @@ -12205,14 +14085,12 @@ "y18n": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", - "dev": true + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" }, "yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, "yaml": { "version": "1.10.0", @@ -12224,7 +14102,6 @@ "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, "requires": { "cliui": "^6.0.0", "decamelize": "^1.2.0", @@ -12243,7 +14120,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, "requires": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -12253,7 +14129,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, "requires": { "p-locate": "^4.1.0" } @@ -12262,7 +14137,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, "requires": { "p-limit": "^2.2.0" } @@ -12270,8 +14144,7 @@ "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" } } }, @@ -12279,7 +14152,6 @@ "version": "18.1.3", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, "requires": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" diff --git a/package.json b/package.json index 086e1dc5890092435b69122b51f35b3ed1a331c9..3bafee71b4d5b8fe6f16d032967a56adaf5d6dbb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ram_server", "private": true, - "version": "1.0.0", + "version": "1.4.0", "description": "Nest TypeScript starter repository", "license": "MIT", "scripts": { @@ -13,6 +13,7 @@ "start:debug": "nodemon --config nodemon-debug.json", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", + "release": "standard-version", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", @@ -22,14 +23,28 @@ "dependencies": { "@nestjs/common": "^7.5.1", "@nestjs/core": "^7.5.1", + "@nestjs/jwt": "^7.2.0", "@nestjs/mongoose": "^7.1.0", + "@nestjs/passport": "^7.1.5", "@nestjs/platform-express": "^7.5.1", + "@nestjs/schedule": "^0.4.1", "@nestjs/swagger": "^4.7.5", + "@types/bcrypt": "^3.0.0", + "bcrypt": "^5.0.0", + "class-transformer": "^0.3.1", + "class-validator": "^0.12.2", "dotenv": "^8.2.0", + "ejs": "^3.1.5", + "form-data": "^3.0.0", + "luxon": "^1.25.0", "mongoose": "^5.10.15", + "passport": "^0.4.1", + "passport-jwt": "^4.0.0", + "passport-local": "^1.0.0", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", "rxjs": "^6.6.3", + "standard-version": "^9.0.0", "swagger-ui-express": "^4.1.5" }, "devDependencies": { @@ -40,6 +55,7 @@ "@types/jest": "^26.0.15", "@types/mongoose": "^5.10.1", "@types/node": "^14.14.6", + "@types/passport-local": "^1.0.33", "@types/supertest": "^2.0.10", "@typescript-eslint/eslint-plugin": "^4.6.1", "@typescript-eslint/parser": "^4.6.1", diff --git a/src/admin/admin.controller.spec.ts b/src/admin/admin.controller.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..0b8ca9028651014a25b02e06c12425dee70ab800 --- /dev/null +++ b/src/admin/admin.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { AdminController } from './admin.controller'; + +describe('AdminController', () => { + let controller: AdminController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [AdminController], + }).compile(); + + controller = module.get<AdminController>(AdminController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/admin/admin.controller.ts b/src/admin/admin.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..fe16c656f87b81aa1387fddd78904e413ab715da --- /dev/null +++ b/src/admin/admin.controller.ts @@ -0,0 +1,50 @@ +import { Body } from '@nestjs/common'; +import { Controller, Get, Post, UseGuards } from '@nestjs/common'; +import { ApiOperation } from '@nestjs/swagger'; +import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; +import { StructuresService } from '../structures/services/structures.service'; +import { Roles } from '../users/decorators/roles.decorator'; +import { RolesGuard } from '../users/guards/roles.guard'; +import { UsersService } from '../users/users.service'; +import { PendingStructureDto } from './dto/pending-structure.dto'; + +@Controller('admin') +export class AdminController { + constructor(private usersService: UsersService, private structuresService: StructuresService) {} + + @UseGuards(JwtAuthGuard, RolesGuard) + @Roles('admin') + @Get('pendingStructures') + @ApiOperation({ description: 'Get pending structre for validation' }) + public getPendingAttachments(): Promise<PendingStructureDto[]> { + return this.usersService.getPendingStructures(); + } + + @UseGuards(JwtAuthGuard, RolesGuard) + @Roles('admin') + @Post('validatePendingStructure') + @ApiOperation({ description: 'Validate structure ownership' }) + public async validatePendingStructure(@Body() pendingStructureDto: PendingStructureDto) { + const structure = await this.structuresService.findOne(pendingStructureDto.structureId); + return this.usersService.validatePendingStructure( + pendingStructureDto.userEmail, + pendingStructureDto.structureId, + structure.structureName, + true + ); + } + + @UseGuards(JwtAuthGuard, RolesGuard) + @Roles('admin') + @Post('rejectPendingStructure') + @ApiOperation({ description: 'Refuse structure ownership' }) + public async refusePendingStructure(@Body() pendingStructureDto: PendingStructureDto) { + const structure = await this.structuresService.findOne(pendingStructureDto.structureId); + return this.usersService.validatePendingStructure( + pendingStructureDto.userEmail, + pendingStructureDto.structureId, + structure.structureName, + false + ); + } +} diff --git a/src/admin/admin.module.ts b/src/admin/admin.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..a13616a1802d24dc4f58d513714a9647193c591b --- /dev/null +++ b/src/admin/admin.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { StructuresModule } from '../structures/structures.module'; +import { UsersModule } from '../users/users.module'; +import { AdminController } from './admin.controller'; +import { AdminService } from './admin.service'; + +@Module({ + imports: [UsersModule, StructuresModule], + controllers: [AdminController], + providers: [AdminService], +}) +export class AdminModule {} diff --git a/src/structures/structures.service.spec.ts b/src/admin/admin.service.spec.ts similarity index 52% rename from src/structures/structures.service.spec.ts rename to src/admin/admin.service.spec.ts index 315e14967a1fbf3ebf0d952557eba03454c6d238..5e5e153df03a8437915bb59ff2861c9366e40f02 100644 --- a/src/structures/structures.service.spec.ts +++ b/src/admin/admin.service.spec.ts @@ -1,15 +1,15 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { StructuresService } from './structures.service'; +import { AdminService } from './admin.service'; -describe('StructuresService', () => { - let service: StructuresService; +describe('AdminService', () => { + let service: AdminService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - providers: [StructuresService], + providers: [AdminService], }).compile(); - service = module.get<StructuresService>(StructuresService); + service = module.get<AdminService>(AdminService); }); it('should be defined', () => { diff --git a/src/admin/admin.service.ts b/src/admin/admin.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..796f9fd1a19a12cbc184a3364dbebe88e2165365 --- /dev/null +++ b/src/admin/admin.service.ts @@ -0,0 +1,4 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class AdminService {} diff --git a/src/admin/dto/pending-structure.dto.ts b/src/admin/dto/pending-structure.dto.ts new file mode 100644 index 0000000000000000000000000000000000000000..67db3710579161c25baefdce81f0462a7e73cdb6 --- /dev/null +++ b/src/admin/dto/pending-structure.dto.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; + +export class PendingStructureDto { + @IsNotEmpty() + @IsEmail() + @ApiProperty({ type: String }) + readonly userEmail: string; + + @IsNotEmpty() + @IsString() + @ApiProperty({ type: String }) + readonly structureId: string; +} diff --git a/src/app.controller.spec.ts b/src/app.controller.spec.ts index a5a45669e4c455e98f794a2b7b1a5c737a3c1b30..3480e92e44e494cff6b5a55ecded5bcc4a65f528 100644 --- a/src/app.controller.spec.ts +++ b/src/app.controller.spec.ts @@ -1,21 +1,20 @@ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; -import { AppService } from './app.service'; - describe('AppController', () => { let app: TestingModule; beforeAll(async () => { app = await Test.createTestingModule({ controllers: [AppController], - providers: [AppService], }).compile(); }); - describe('getHello', () => { - it('should return "Hello World!"', () => { + describe('healthcheck', () => { + it('should return "Hello World!"', async () => { const appController = app.get<AppController>(AppController); - expect(appController.getHello()).toBe('Hello World!'); + const result = { status: 'API Online', uptime: 1 }; + jest.spyOn(appController, 'healthcheck').mockImplementation(async (): Promise<{ status; uptime }> => result); + expect(await appController.healthcheck()).toBe(result); }); }); }); diff --git a/src/app.module.ts b/src/app.module.ts index 317caaab60e78a921488aa404f57fde2e6da13c5..e0aa57e2278e56f027bcfe0d0db969e5936708c8 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,19 +1,31 @@ import { Module } from '@nestjs/common'; import { MongooseModule } from '@nestjs/mongoose'; +import { ScheduleModule } from '@nestjs/schedule'; import { AppController } from './app.controller'; import { StructuresModule } from './structures/structures.module'; import { ConfigurationModule } from './configuration/configuration.module'; import { CategoriesModule } from './categories/categories.module'; +import { AuthModule } from './auth/auth.module'; +import { UsersModule } from './users/users.module'; import { MailerModule } from './mailer/mailer.module'; +import { TclModule } from './tcl/tcl.module'; +import { AdminModule } from './admin/admin.module'; +import { PostsModule } from './posts/posts.module'; @Module({ imports: [ ConfigurationModule, MongooseModule.forRoot( `mongodb://${process.env.MONGO_NON_ROOT_USERNAME}:${process.env.MONGO_NON_ROOT_PASSWORD}@${process.env.MONGO_DB_HOST_AND_PORT}/ram` ), + ScheduleModule.forRoot(), StructuresModule, CategoriesModule, + AuthModule, + UsersModule, MailerModule, + TclModule, + AdminModule, + PostsModule, ], controllers: [AppController], }) diff --git a/src/auth/auth.controller.spec.ts b/src/auth/auth.controller.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..52a4184107d34402e9bdd97e8d5e9dfd27209fb4 --- /dev/null +++ b/src/auth/auth.controller.spec.ts @@ -0,0 +1,43 @@ +import { JwtModule } from '@nestjs/jwt'; +import { getModelToken } from '@nestjs/mongoose'; +import { PassportModule } from '@nestjs/passport'; +import { Test, TestingModule } from '@nestjs/testing'; +import { ConfigurationModule } from '../configuration/configuration.module'; +import { MailerModule } from '../mailer/mailer.module'; +import { User } from '../users/schemas/user.schema'; +import { UsersService } from '../users/users.service'; +import { AuthController } from './auth.controller'; +import { AuthService } from './auth.service'; + +describe('AuthController', () => { + let controller: AuthController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [ + PassportModule, + MailerModule, + ConfigurationModule, + JwtModule.register({ + secret: process.env.JWT_SECRET, + signOptions: { expiresIn: '86400s' }, // 24h validity + }), + ], + controllers: [AuthController], + providers: [ + AuthService, + UsersService, + { + provide: getModelToken('User'), + useValue: User, + }, + ], + }).compile(); + + controller = module.get<AuthController>(AuthController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..63c11822e28d603d43b5fc766b70c50b9d244196 --- /dev/null +++ b/src/auth/auth.controller.ts @@ -0,0 +1,13 @@ +import { Controller, Post, Body } from '@nestjs/common'; +import { AuthService } from './auth.service'; +import { LoginDto } from './login-dto'; + +@Controller('auth') +export class AuthController { + constructor(private authService: AuthService) {} + + @Post('login') + async login(@Body() loginDto: LoginDto) { + return this.authService.login(loginDto); + } +} diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..13ad33705043b02a50001703ddf6b8a35d6032d1 --- /dev/null +++ b/src/auth/auth.module.ts @@ -0,0 +1,21 @@ +import { Module } from '@nestjs/common'; +import { JwtModule } from '@nestjs/jwt'; +import { PassportModule } from '@nestjs/passport'; +import { AuthService } from './auth.service'; +import { UsersModule } from '../users/users.module'; +import { AuthController } from './auth.controller'; +import { JwtStrategy } from './strategy/jwt.strategy'; + +@Module({ + imports: [ + UsersModule, + PassportModule, + JwtModule.register({ + secret: process.env.JWT_SECRET, + signOptions: { expiresIn: '86400s' }, // 24h validity + }), + ], + providers: [AuthService, JwtStrategy], + controllers: [AuthController], +}) +export class AuthModule {} diff --git a/src/auth/auth.service.spec.ts b/src/auth/auth.service.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..2e85b13e99460b761605d520d20d936392dc2d9c --- /dev/null +++ b/src/auth/auth.service.spec.ts @@ -0,0 +1,96 @@ +import { HttpException, HttpStatus } from '@nestjs/common'; +import { JwtModule } from '@nestjs/jwt'; +import { getModelToken } from '@nestjs/mongoose'; +import { PassportModule } from '@nestjs/passport'; +import { Test, TestingModule } from '@nestjs/testing'; +import { ConfigurationModule } from '../configuration/configuration.module'; +import { MailerModule } from '../mailer/mailer.module'; +import { User } from '../users/schemas/user.schema'; +import { UsersService } from '../users/users.service'; +import { AuthService } from './auth.service'; +import { LoginDto } from './login-dto'; + +describe('AuthService', () => { + let service: AuthService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [ + PassportModule, + MailerModule, + ConfigurationModule, + JwtModule.register({ + secret: process.env.JWT_SECRET, + signOptions: { expiresIn: '86400s' }, // 24h validity + }), + ], + providers: [ + AuthService, + UsersService, + { + provide: getModelToken('User'), + useValue: User, + }, + ], + }).compile(); + + service = module.get<AuthService>(AuthService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + describe('validateUser', () => { + it('should validateUser', async () => { + const result = { + _id: 'tsfsf6296', + validationToken: + 'cf1c74c22cedb6b575945098db42d2f493fb759c9142c6aff7980f252886f36ee086574ee99a06bc99119079257116c959c8ec870949cebdef2b293666dbca42', + emailVerified: false, + email: 'jacques.dupont@mii.com', + role: 0, + }; + const loginDto: LoginDto = { email: 'jacques.dupont@mii.com', password: 'test1A!!' }; //NOSONAR + jest.spyOn(service, 'validateUser').mockImplementation(async (): Promise<any> => result); + expect(await service.validateUser(loginDto)).toBe(result); + }); + + it('should not validateUser', async () => { + const result = null; + const loginDto: LoginDto = { email: 'jacques.dupont@mii.com', password: 'test1A!!' }; //NOSONAR + jest.spyOn(service, 'validateUser').mockImplementation(async (): Promise<any> => result); + expect(await service.validateUser(loginDto)).toBe(result); + }); + }); + + describe('login', () => { + it('should login user jacques.dupont@mii.com', async () => { + const result = { username: ' jacques.dupont@mii.com', token: 'tok3n!1sfq' }; + const loginDto: LoginDto = { email: 'jacques.dupont@mii.com', password: 'test1A!!' }; //NOSONAR + jest.spyOn(service, 'validateUser').mockImplementation(async (): Promise<{ username; token }> => result); + expect(await service.validateUser(loginDto)).toBe(result); + }); + + it('should not login jacques.dupont@mii.com, email not verified', async () => { + const result = new HttpException('Invalid credentials', HttpStatus.UNAUTHORIZED); + const loginDto: LoginDto = { email: 'jacques.dupont@mii.com', password: 'test1A!!' }; //NOSONAR + jest.spyOn(service, 'validateUser').mockImplementation(async (): Promise<any> => result); + expect(await service.validateUser(loginDto)).toBe(result); + }); + + it('should not login jacques.dupont@mii.com, bad password', async () => { + const result = new HttpException('Invalid credentials', HttpStatus.UNAUTHORIZED); + const loginDto: LoginDto = { email: 'jacques.dupont@mii.com', password: 'test1A!!' }; //NOSONAR + jest.spyOn(service, 'validateUser').mockImplementation(async (): Promise<any> => result); + expect(await service.validateUser(loginDto)).toBe(result); + }); + + it('should not login jacques.dupont@mii.com, username does not exist', async () => { + const result = new HttpException('Invalid credentials', HttpStatus.UNAUTHORIZED); + const loginDto: LoginDto = { email: 'jacques.dupont@mii.com', password: 'test1A!!' }; //NOSONAR + jest.spyOn(service, 'validateUser').mockImplementation(async (): Promise<any> => result); + expect(await service.validateUser(loginDto)).toBe(result); + }); + }); +}); diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..f14cd8bf262a38d0a2550589ebb9f349bb262e8c --- /dev/null +++ b/src/auth/auth.service.ts @@ -0,0 +1,45 @@ +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { UsersService } from '../users/users.service'; +import { JwtService } from '@nestjs/jwt'; +import { LoginDto } from './login-dto'; +import { DateTime } from 'luxon'; +import { User } from '../users/schemas/user.schema'; + +@Injectable() +export class AuthService { + constructor(private usersService: UsersService, private jwtService: JwtService) {} + + async validateUser(loginDto: LoginDto): Promise<any> { + const user = await this.usersService.findOne(loginDto.email); + if (user) { + return user; + } + return null; + } + + async login(loginDto: LoginDto): Promise<{ username; token }> { + // find user in db + const user: User = await this.usersService.findByLogin(loginDto); + if (!user.emailVerified) { + throw new HttpException('Invalid credentials', HttpStatus.UNAUTHORIZED); + } + + // generate and sign token + const token = this._createToken(user); + + return { + username: user.email, + name: user.name, + surname: user.surname, + ...token, + }; + } + + private _createToken(user: User): any { + const local = DateTime.local().setZone('Europe/Paris'); + return { + accessToken: this.jwtService.sign({ email: user.email, role: user.role }), + expiresAt: local.plus({ days: 1 }), + }; + } +} diff --git a/src/auth/guards/jwt-auth.guard.ts b/src/auth/guards/jwt-auth.guard.ts new file mode 100644 index 0000000000000000000000000000000000000000..2155290edea420d9d7e339f20679f3114d4c2d69 --- /dev/null +++ b/src/auth/guards/jwt-auth.guard.ts @@ -0,0 +1,5 @@ +import { Injectable } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; + +@Injectable() +export class JwtAuthGuard extends AuthGuard('jwt') {} diff --git a/src/auth/login-dto.ts b/src/auth/login-dto.ts new file mode 100644 index 0000000000000000000000000000000000000000..c359b50d76129edc93f5954c6f52b60d0017378c --- /dev/null +++ b/src/auth/login-dto.ts @@ -0,0 +1,6 @@ +import { IsNotEmpty } from 'class-validator'; + +export class LoginDto { + @IsNotEmpty() readonly email: string; + @IsNotEmpty() readonly password: string; +} diff --git a/src/auth/strategy/jwt.strategy.ts b/src/auth/strategy/jwt.strategy.ts new file mode 100644 index 0000000000000000000000000000000000000000..78f08c9e13f0983d63c2aa94b755a603a48ca60b --- /dev/null +++ b/src/auth/strategy/jwt.strategy.ts @@ -0,0 +1,23 @@ +import { ExtractJwt, Strategy } from 'passport-jwt'; +import { PassportStrategy } from '@nestjs/passport'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { AuthService } from '../auth.service'; + +@Injectable() +export class JwtStrategy extends PassportStrategy(Strategy) { + constructor(private authService: AuthService) { + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + ignoreExpiration: false, + secretOrKey: process.env.JWT_SECRET, + }); + } + + async validate(payload: any) { + const user = await this.authService.validateUser(payload); + if (!user) { + throw new HttpException('Invalid token', HttpStatus.UNAUTHORIZED); + } + return user; + } +} diff --git a/src/categories/controllers/categories-accompagnement.controller.ts b/src/categories/controllers/categories-accompagnement.controller.ts index a3333be16a7c5c71ce3820fd3af25efc1a9cb9c8..66ea90536b84624505ba92e826f7b3cbe9fa2b9d 100644 --- a/src/categories/controllers/categories-accompagnement.controller.ts +++ b/src/categories/controllers/categories-accompagnement.controller.ts @@ -1,5 +1,5 @@ import { Body, Controller, Get, Post } from '@nestjs/common'; -import { CategoriesAccompagnementService } from '../services/categories-Accompagnement.service'; +import { CategoriesAccompagnementService } from '../services/categories-accompagnement.service'; import { CreateCategoriesAccompagnement } from '../dto/create-categoriesAccompagnement.dto'; import { CategoriesAccompagnement } from '../schemas/categoriesAccompagnement.schema'; diff --git a/src/categories/controllers/categories-others.controller.ts b/src/categories/controllers/categories-others.controller.ts index c535e195eab64c90e65849f658c95e1a7b626937..6e284e397b877f14417040ba36d36516bb15e1eb 100644 --- a/src/categories/controllers/categories-others.controller.ts +++ b/src/categories/controllers/categories-others.controller.ts @@ -1,5 +1,5 @@ import { Body, Controller, Get, Post } from '@nestjs/common'; -import { CategoriesOthersService } from '../services/categories-Others.service'; +import { CategoriesOthersService } from '../services/categories-others.service'; import { CreateCategoriesOthers } from '../dto/create-categoriesOthers.dto'; import { CategoriesOthers } from '../schemas/categoriesOthers.schema'; diff --git a/src/categories/services/categories-formations.service.spec.ts b/src/categories/services/categories-formations.service.spec.ts deleted file mode 100644 index 19cd6f1edc7c4915bedcb08490c0b31a9b22a28c..0000000000000000000000000000000000000000 --- a/src/categories/services/categories-formations.service.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { CategoriesFormationsService } from './categories-formations.service'; - -describe('CategoriesFormationsService', () => { - let service: CategoriesFormationsService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [CategoriesFormationsService], - }).compile(); - - service = module.get<CategoriesFormationsService>(CategoriesFormationsService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); diff --git a/src/configuration/config.dev.ts b/src/configuration/config.dev.ts new file mode 100644 index 0000000000000000000000000000000000000000..e9763084fa20e80748ce3f82c33fe8f3517bc528 --- /dev/null +++ b/src/configuration/config.dev.ts @@ -0,0 +1,37 @@ +export const configDev = { + url: process.env.MAIL_URL, + token: process.env.MAIL_TOKEN, + host: 'ram-dev.grandlyon.com', + protocol: 'https', + port: '443', + from: 'inclusionnumerique@grandlyon.com', + from_name: 'Réseau des acteurs de la médiation numérique', + replyTo: 'inclusionnumerique@grandlyon.com', + templates: { + directory: './src/mailer/mail-templates', + verify: { + ejs: 'verify.ejs', + json: 'verify.json', + }, + changeEmail: { + ejs: 'changeEmail.ejs', + json: 'changeEmail.json', + }, + resetPassword: { + ejs: 'resetPassword.ejs', + json: 'resetPassword.json', + }, + adminStructureClaim: { + ejs: 'adminStructureClaim.ejs', + json: 'adminStructureClaim.json', + }, + structureClaimValidation: { + ejs: 'structureClaimValidation.ejs', + json: 'structureClaimValidation.json', + }, + structureOutdatedInfo: { + ejs: 'structureOutdatedInfo.ejs', + json: 'structureOutdatedInfo.json', + }, + }, +}; diff --git a/src/configuration/config.prod.ts b/src/configuration/config.prod.ts new file mode 100644 index 0000000000000000000000000000000000000000..7c1b236c9b2d50c67e4769354b0d3dcded212fa8 --- /dev/null +++ b/src/configuration/config.prod.ts @@ -0,0 +1,37 @@ +export const configProd = { + url: process.env.MAIL_URL, + token: process.env.MAIL_TOKEN, + host: 'ram.grandlyon.com', + protocol: 'https', + port: '443', + from: 'inclusionnumerique@grandlyon.com', + from_name: 'Réseau des acteurs de la médiation numérique', + replyTo: 'inclusionnumerique@grandlyon.com', + templates: { + directory: './src/mailer/mail-templates', + verify: { + ejs: 'verify.ejs', + json: 'verify.json', + }, + changeEmail: { + ejs: 'changeEmail.ejs', + json: 'changeEmail.json', + }, + resetPassword: { + ejs: 'resetPassword.ejs', + json: 'resetPassword.json', + }, + adminStructureClaim: { + ejs: 'adminStructureClaim.ejs', + json: 'adminStructureClaim.json', + }, + structureClaimValidation: { + ejs: 'structureClaimValidation.ejs', + json: 'structureClaimValidation.json', + }, + structureOutdatedInfo: { + ejs: 'structureOutdatedInfo.ejs', + json: 'structureOutdatedInfo.json', + }, + }, +}; diff --git a/src/configuration/config.ts b/src/configuration/config.ts index ee0b9db0dbad8115e0bafe32c87cead508ac0490..7bb9d6f5a8b02c9fc654caa84f5cb74a223c99e9 100644 --- a/src/configuration/config.ts +++ b/src/configuration/config.ts @@ -1,9 +1,9 @@ export const config = { url: process.env.MAIL_URL, token: process.env.MAIL_TOKEN, - host: 'ram.grandlyon.com', - protocol: 'https', - port: '443', + host: 'localhost', + protocol: 'http', + port: '4200', from: 'inclusionnumerique@grandlyon.com', from_name: 'Réseau des acteurs de la médiation numérique', replyTo: 'inclusionnumerique@grandlyon.com', @@ -17,5 +17,25 @@ export const config = { ejs: 'changeEmail.ejs', json: 'changeEmail.json', }, + resetPassword: { + ejs: 'resetPassword.ejs', + json: 'resetPassword.json', + }, + adminStructureClaim: { + ejs: 'adminStructureClaim.ejs', + json: 'adminStructureClaim.json', + }, + structureClaimValidation: { + ejs: 'structureClaimValidation.ejs', + json: 'structureClaimValidation.json', + }, + structureOutdatedInfo: { + ejs: 'structureOutdatedInfo.ejs', + json: 'structureOutdatedInfo.json', + }, + apticStructureDuplication: { + ejs: 'apticStructureDuplication.ejs', + json: 'apticStructureDuplication.json', + }, }, }; diff --git a/src/configuration/configuration.service.ts b/src/configuration/configuration.service.ts index 09d6666ca4b1ea1f417218e794f25db463b21789..13b4f5e4d827e55bc85ba4cacb6dc54ada5c887c 100644 --- a/src/configuration/configuration.service.ts +++ b/src/configuration/configuration.service.ts @@ -1,10 +1,23 @@ +import { Logger } from '@nestjs/common'; import * as dotenv from 'dotenv'; import { config } from './config'; +import { configProd } from './config.prod'; +import { configDev } from './config.dev'; export class ConfigurationService { - private _config = config; + private readonly _config; constructor() { // Initializing conf with values from var env + if (process.env.NODE_ENV && process.env.NODE_ENV === 'production') { + this._config = configProd; + Logger.log('App started with production conf', 'ConfigurationService'); + } else if (process.env.NODE_ENV && process.env.NODE_ENV === 'dev') { + this._config = configDev; + Logger.log('App started with dev conf', 'ConfigurationService'); + } else { + this._config = config; + Logger.log('App started with local conf', 'ConfigurationService'); + } dotenv.config(); } diff --git a/src/mailer/mail-templates/adminStructureClaim.ejs b/src/mailer/mail-templates/adminStructureClaim.ejs new file mode 100644 index 0000000000000000000000000000000000000000..12ba539835a44a9a609bd7d1937d45ec48e09474 --- /dev/null +++ b/src/mailer/mail-templates/adminStructureClaim.ejs @@ -0,0 +1,6 @@ +Bonjour<br /> +<br /> +Une nouvelle structure a été revendiquée. Pour valider ou refuser la demande, merci de vous rendre sur +<a href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/admin">ce lien</a>. +<br /> +Ce mail est un mail automatique. Merci de ne pas y répondre. diff --git a/src/mailer/mail-templates/adminStructureClaim.json b/src/mailer/mail-templates/adminStructureClaim.json new file mode 100644 index 0000000000000000000000000000000000000000..bad53f08f4c0debbe28b988b65f37b6a3902ddb2 --- /dev/null +++ b/src/mailer/mail-templates/adminStructureClaim.json @@ -0,0 +1,3 @@ +{ + "subject": "Nouvelle demande de revendication de structure" +} diff --git a/src/mailer/mail-templates/apticStructureDuplication.ejs b/src/mailer/mail-templates/apticStructureDuplication.ejs new file mode 100644 index 0000000000000000000000000000000000000000..5ac784a9c9e172e1d90179224c78319e5c46fb25 --- /dev/null +++ b/src/mailer/mail-templates/apticStructureDuplication.ejs @@ -0,0 +1,13 @@ +Bonjour,<br /> +<br /> +La fiche structure: <strong><%= name %></strong> a été créée après récupération des données aptic. Elle correspond +potientiellement a la structure existante : <strong><%= duplicatedStructureName %></strong>. +<br /> +<br /> +Cordialement, +<br /> +<br /> +L'équipe RES'in +<br /> +<br /> +Ce mail est un mail automatique. Merci de ne pas y répondre. diff --git a/src/mailer/mail-templates/apticStructureDuplication.json b/src/mailer/mail-templates/apticStructureDuplication.json new file mode 100644 index 0000000000000000000000000000000000000000..ec9e77ef58fb1e582b39f4aac4e7a8fef148ecff --- /dev/null +++ b/src/mailer/mail-templates/apticStructureDuplication.json @@ -0,0 +1,3 @@ +{ + "subject": "Doublon Aptic" +} diff --git a/src/mailer/mail-templates/resetPassword.ejs b/src/mailer/mail-templates/resetPassword.ejs new file mode 100644 index 0000000000000000000000000000000000000000..7143e2421b42f6cf5acd2b6b81a7d4372d8e847b --- /dev/null +++ b/src/mailer/mail-templates/resetPassword.ejs @@ -0,0 +1,12 @@ +Bonjour<br /> +<br /> +Vous avez demandé une réinitialisation de votre mot de passe pour le +<em>Réseau des Acteurs de la Médiation Numérique de la Métropole de Lyon</em>. Pour changer de mot de passe, merci de +cliquer sur le lien suivant : +<a + href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/reset-password?token=<%= token %>" + >ce lien</a +><br /> +Si vous n'avez pas demander de réinitiallisation de votre mot de passe, merci d'ignorer cet email. +<br /> +Ce mail est un mail automatique. Merci de ne pas y répondre. diff --git a/src/mailer/mail-templates/resetPassword.json b/src/mailer/mail-templates/resetPassword.json new file mode 100644 index 0000000000000000000000000000000000000000..c5fe69522794c8f40ea20e57eb340aec8e9728f5 --- /dev/null +++ b/src/mailer/mail-templates/resetPassword.json @@ -0,0 +1,3 @@ +{ + "subject": "Réinitialisation de mot de passe" +} diff --git a/src/mailer/mail-templates/structureClaimValidation.ejs b/src/mailer/mail-templates/structureClaimValidation.ejs new file mode 100644 index 0000000000000000000000000000000000000000..7ede22e62c01ebf3e0a53ee487763fda1f51194a --- /dev/null +++ b/src/mailer/mail-templates/structureClaimValidation.ejs @@ -0,0 +1,7 @@ +Bonjour<br /> +<br /> +La demande de rattachement de votre compte a la structure <strong><%= name %></strong> a été +<strong><%= status %></strong>. +<br /> +<br /> +Ce mail est un mail automatique. Merci de ne pas y répondre. diff --git a/src/mailer/mail-templates/structureClaimValidation.json b/src/mailer/mail-templates/structureClaimValidation.json new file mode 100644 index 0000000000000000000000000000000000000000..bdcb607dc59c9beed42c21a607287be19c06e217 --- /dev/null +++ b/src/mailer/mail-templates/structureClaimValidation.json @@ -0,0 +1,3 @@ +{ + "subject": "Votre demande de rattachement, Réseau des Acteurs de la Médiation Numérique de la Métropole de Lyon" +} diff --git a/src/mailer/mail-templates/structureOutdatedInfo.ejs b/src/mailer/mail-templates/structureOutdatedInfo.ejs new file mode 100644 index 0000000000000000000000000000000000000000..49e0fa1eef8672da8248b137ee0cb578cce41c06 --- /dev/null +++ b/src/mailer/mail-templates/structureOutdatedInfo.ejs @@ -0,0 +1,15 @@ +Bonjour<br /> +<br /> +Vous recevez ce message, parce que votre structure <strong><%= name %></strong> est référencée sur RES'in, le réseau des +acteurs de l'inclusion numérique de la Métropole de Lyon. Pouvez-vous nous aider en vérifiant que vos données sont bien +à jour en +<a href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/home?id=<%= id %>" + >cliquant ici</a +>. +<br /> +Cordialement, +<br /> +L'équipe RES'in +<br /> +<br /> +Ce mail est un mail automatique. Merci de ne pas y répondre. diff --git a/src/mailer/mail-templates/structureOutdatedInfo.json b/src/mailer/mail-templates/structureOutdatedInfo.json new file mode 100644 index 0000000000000000000000000000000000000000..1782ea483e33fb07f29c92e7a12f758f23f2e395 --- /dev/null +++ b/src/mailer/mail-templates/structureOutdatedInfo.json @@ -0,0 +1,3 @@ +{ + "subject": "Votre fiche structure n'est plus a jour, Réseau des Acteurs de la Médiation Numérique de la Métropole de Lyon" +} diff --git a/src/mailer/mail-templates/verify.ejs b/src/mailer/mail-templates/verify.ejs index e384dae43ff76ded6f735fe586b80598c6f8fb14..2782ec1376354879a3875a0ebf3bc8e5352fe4ad 100644 --- a/src/mailer/mail-templates/verify.ejs +++ b/src/mailer/mail-templates/verify.ejs @@ -2,7 +2,7 @@ Bonjour<br /> <br /> Afin de pouvoir vous connecter sur la plateforme, merci de cliquer sur <a - href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/users/verify/<%= token %>" + href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/users/verify/<%= userId %>?token=<%= token %>" >ce lien</a > afin de valider votre inscription<br /> diff --git a/src/mailer/mailer.module.ts b/src/mailer/mailer.module.ts index e7b3a0e6a361722b7b16ae713fca532a546f02e2..576cf55813c7bfbf898334f093bb2f64b198cffe 100644 --- a/src/mailer/mailer.module.ts +++ b/src/mailer/mailer.module.ts @@ -1,10 +1,11 @@ import { Module } from '@nestjs/common'; import { HttpModule } from '@nestjs/common/http/http.module'; +import { ConfigurationService } from '../configuration/configuration.service'; import { MailerService } from './mailer.service'; @Module({ imports: [HttpModule], - providers: [MailerService], + providers: [MailerService, ConfigurationService], exports: [MailerService], }) export class MailerModule {} diff --git a/src/mailer/mailer.service.spec.ts b/src/mailer/mailer.service.spec.ts index 1ab5e828cc63cdd85859586764c0ca2a9d4dc882..086056cab0a7d8efe90a3fde1e61ac0b833e84ee 100644 --- a/src/mailer/mailer.service.spec.ts +++ b/src/mailer/mailer.service.spec.ts @@ -1,4 +1,6 @@ +import { HttpModule } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; +import { ConfigurationService } from '../configuration/configuration.service'; import { MailerService } from './mailer.service'; describe('MailerService', () => { @@ -6,7 +8,8 @@ describe('MailerService', () => { beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - providers: [MailerService], + imports: [HttpModule], + providers: [MailerService, ConfigurationService], }).compile(); service = module.get<MailerService>(MailerService); diff --git a/src/mailer/mailer.service.ts b/src/mailer/mailer.service.ts index fdebbee315d40fb73dcb0afcc5ae233f8715c932..04027e30e550dde3f5b8bb793a89609f4299d87b 100644 --- a/src/mailer/mailer.service.ts +++ b/src/mailer/mailer.service.ts @@ -1,7 +1,9 @@ import { HttpService, Injectable, Logger } from '@nestjs/common'; -import { ConfigurationService } from '../configuration/configuration.service'; +import { AxiosResponse } from 'axios'; import * as fs from 'fs'; import * as path from 'path'; +import * as FormData from 'form-data'; +import { ConfigurationService } from '../configuration/configuration.service'; @Injectable() export class MailerService { public config = null; @@ -19,31 +21,37 @@ export class MailerService { * @param {string} html * @param {string} text */ - public send(to: string, subject: string, html: string): Promise<any> { + public async send(to: string, subject: string, html: string): Promise<AxiosResponse<any>> { + const formData = new FormData(); const data = JSON.stringify({ - from: this.config.from, + // eslint-disable-next-line camelcase + from_email: this.config.from, // eslint-disable-next-line camelcase from_name: this.config.from_name, - to: to, + to: [{ email: to }], + reply_to: 'inclusionnumerique@grandlyon.com', subject: subject, content: html, }); - Logger.log(`[Mailer] Send mail : ${subject}`); + formData.append('metadata', data); + const contentLength = formData.getLengthSync(); + Logger.log(`Send mail : ${subject}`, 'Mailer'); return new Promise((resolve, reject) => { this.httpService - .post(process.env.MAIL_URL, data, { + .post(process.env.MAIL_URL, formData, { headers: { - 'Content-Type': 'application/json', + 'Content-Length': contentLength, Authorization: 'Bearer ' + process.env.MAIL_TOKEN, + ...formData.getHeaders(), }, }) .subscribe( (body) => { - Logger.log(`[Mailer] Send mail : ${subject} success`); + Logger.log(`Send mail - success : ${subject}`, 'Mailer'); return resolve(body); }, (err) => { - Logger.error(err); + Logger.error(err, 'Mailer'); return reject(err); } ); diff --git a/src/main.ts b/src/main.ts index 65ac8383570553afd58e1b089359a71bd922ceb5..d4287f5015f44ad7e5e76f6ab93023cd41caf1d7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,11 +1,17 @@ +import { ValidationPipe } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); - - const options = new DocumentBuilder().setTitle('RAM').setDescription('RAM API description').setVersion('1.0').build(); + app.useGlobalPipes(new ValidationPipe()); + const options = new DocumentBuilder() + .setTitle('RAM') + .setDescription('RAM API description') + .setVersion('1.0') + .addBearerAuth({ type: 'http', scheme: 'bearer', bearerFormat: 'JWT' }, 'JWT') + .build(); const document = SwaggerModule.createDocument(app, options); SwaggerModule.setup('api', app, document); await app.listen(3000); diff --git a/src/posts/posts.controller.spec.ts b/src/posts/posts.controller.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..7784335f5040742d323ac81d6e04f2940f8e9088 --- /dev/null +++ b/src/posts/posts.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { PostsController } from './posts.controller'; + +describe('PostsController', () => { + let controller: PostsController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [PostsController], + }).compile(); + + controller = module.get<PostsController>(PostsController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/posts/posts.controller.ts b/src/posts/posts.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..e5c9478a2cde8a29629759fab5fbf5513c07acc0 --- /dev/null +++ b/src/posts/posts.controller.ts @@ -0,0 +1,35 @@ +import { Controller, Get, HttpService, Query } from '@nestjs/common'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { ApiBearerAuth, ApiQuery } from '@nestjs/swagger'; +import { Post } from './schemas/post.schema'; + +@Controller('posts') +export class PostsController { + constructor(private readonly httpService: HttpService) {} + + @Get() + @ApiQuery({ name: 'include', type: String, required: false }) + @ApiQuery({ name: 'fields', type: String, required: false }) + @ApiQuery({ name: 'formats', type: String, required: false }) + @ApiQuery({ name: 'filter', type: String, required: false }) + @ApiQuery({ name: 'limit', type: String, required: false }) + @ApiQuery({ name: 'page', type: String, required: false }) + @ApiQuery({ name: 'order', type: String, required: false }) + public async findAll(@Query() query): Promise<Observable<{ posts: Post[] }>> { + return this.httpService + .get(`${process.env.GHOST_HOST_AND_PORT}/ghost/api/v3/content/posts`, { + params: { + key: process.env.GHOST_CONTENT_API_KEY, + include: query.include ? query.include : null, + fields: query.fields ? query.fields : null, + formats: query.formats ? query.formats : null, + filter: query.filter ? query.filter : null, + limit: query.limit ? query.limit : null, + order: query.order ? query.order : null, + page: query.page ? query.page : null, + }, + }) + .pipe(map((response) => response.data)); + } +} diff --git a/src/posts/posts.module.ts b/src/posts/posts.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..de11cb04f87397833dacfd9550c8947acfc9c591 --- /dev/null +++ b/src/posts/posts.module.ts @@ -0,0 +1,8 @@ +import { HttpModule, Module } from '@nestjs/common'; +import { PostsController } from './posts.controller'; + +@Module({ + imports: [HttpModule], + controllers: [PostsController], +}) +export class PostsModule {} diff --git a/src/posts/schemas/post.schema.ts b/src/posts/schemas/post.schema.ts new file mode 100644 index 0000000000000000000000000000000000000000..1711ec1f208295ec71881e4b480f21cd264f2fee --- /dev/null +++ b/src/posts/schemas/post.schema.ts @@ -0,0 +1,29 @@ +export class Post { + id: string; + uuid: string; + title: string; + slug: string; + html: string; + comment_id: string; + feature_image: null; + featured: false; + visibility: string; + email_recipient_filter: string; + created_at: string; + updated_at: string; + published_at: string; + url: string; + excerpt: string; + reading_time: string; + access: boolean; + send_email_when_published: boolean; + og_image: string; + og_title: string; + og_description: string; + twitter_image: string; + twitter_title: string; + twitter_description: string; + meta_title: string; + meta_description: string; + email_subject: string; +} diff --git a/src/structures/dto/create-structure.dto.ts b/src/structures/dto/create-structure.dto.ts index 857697914c2aad758535f706b2e2f0811fb3e2bd..ee9cc22565d0a7e4052c8ef3f3f3b961d43e4d75 100644 --- a/src/structures/dto/create-structure.dto.ts +++ b/src/structures/dto/create-structure.dto.ts @@ -1,55 +1,12 @@ -import { Week } from '../schemas/week.schema'; +import { Type } from 'class-transformer'; +import { IsNotEmpty, ValidateNested } from 'class-validator'; +import { structureDto } from './structure.dto'; export class CreateStructureDto { - id: number; - numero: number; - dateDeCreation: string; - derniereModification: string; - nomDeLusager: string; - votreStructureEstElle: string; - nomDeVotreStructure: string; - description: string; - activitesMaintenuesDansLeCadreDuConfinement: string; - n: string; - voie: string; - telephone: string; - courriel: string; - siteWeb: string; - facebook: string; - twitter: string; - instagram: string; - civilite: string; - nom: string; - prenom: string; - fonction: string; - accessibilitePersonnesAMobiliteReduitePmr: string; - modalitesDacces: string[]; - labelsEtQualifications: string[]; - publicsAcceptes: string[]; - fermeturesExceptionnelles: string; - jaccompagneLesUsagersDansLeursDemarchesEnLigne: boolean; - accompagnementDesDemarches: string[]; - autresAccompagnements: string; - lesCompetencesDeBase: string[]; - accesAuxDroits: string[]; - insertionSocialeEtProfessionnelle: string[]; - aideALaParentalite: string[]; - cultureEtSecuriteNumerique: string[]; - wifiEnAccesLibre: boolean; - nbComputers: boolean; - nombre: string; - tablettes: boolean; - bornesNumeriques: boolean; - imprimantes: boolean; - precisionsSiNecessaire: string; - statutJuridique: string; - appartenezVousAUnReseauDeMediation: string; - precisezLequel: string; - idDeLitemStructureDansDirectus: string; - statutDeLitemStructureDansDirectus: string; - idDeLitemOffreDansDirectus: string; - statut: string; - typeDeStructure: string[]; - commune: string; - hours: Week; + @ValidateNested({ each: true }) + @Type(() => structureDto) + structure: structureDto; + + @IsNotEmpty() + idUser: string; } diff --git a/src/structures/dto/structure.dto.ts b/src/structures/dto/structure.dto.ts new file mode 100644 index 0000000000000000000000000000000000000000..01c7a962917949ce57fcaed7a0f3a424a3a00feb --- /dev/null +++ b/src/structures/dto/structure.dto.ts @@ -0,0 +1,72 @@ +import { Type } from 'class-transformer'; +import { ArrayNotEmpty, IsNotEmpty, ValidateNested } from 'class-validator'; +import { Address } from '../schemas/address.schema'; +import { Week } from '../schemas/week.schema'; + +export class structureDto { + numero: string; + createdAt: string; + updatedAt: string; + + @IsNotEmpty() + structureName: string; + + @IsNotEmpty() + structureType: string; + + description: string; + + @ValidateNested({ each: true }) + @Type(() => Address) + address: Address; + + @IsNotEmpty() + contactPhone: string; + + @IsNotEmpty() + contactMail: string; + + website: string; + facebook: string; + twitter: string; + instagram: string; + linkedin: string; + + lockdownActivity: string; + otherDescription: string; + @IsNotEmpty() + pmrAccess: boolean; + publicsAccompaniment: string[]; + proceduresAccompaniment: string[]; + @ArrayNotEmpty() + accessModality: string[]; + + labelsQualifications: string[]; + + @ArrayNotEmpty() + publics: string[]; + @IsNotEmpty() + freeWifi: boolean; + @IsNotEmpty() + freeWorkShop: boolean; + @IsNotEmpty() + nbComputers: number; + @IsNotEmpty() + nbPrinters: number; + @IsNotEmpty() + nbTablets: number; + @IsNotEmpty() + nbNumericTerminal: number; + @IsNotEmpty() + nbScanners: number; + exceptionalClosures: string; + equipmentsAndServices: string[]; + hours: Week; + baseSkills: string[]; + accessRight: string[]; + parentingHelp: string[]; + socialAndProfessional: string[]; + digitalCultureSecurity: string[]; + coord: number[]; + accountVerified: boolean; +} diff --git a/src/structures/schemas/address.schema.ts b/src/structures/schemas/address.schema.ts new file mode 100644 index 0000000000000000000000000000000000000000..f0d34d8f46936c871bb1299f88f9445d80c8dcd9 --- /dev/null +++ b/src/structures/schemas/address.schema.ts @@ -0,0 +1,17 @@ +import { SchemaFactory } from '@nestjs/mongoose'; +import { IsNotEmpty } from 'class-validator'; + +export type AddressDocument = Address & Document; + +export class Address { + @IsNotEmpty() + numero: string; + + @IsNotEmpty() + street: string; + + @IsNotEmpty() + commune: string; +} + +export const AddressSchema = SchemaFactory.createForClass(Address); diff --git a/src/structures/schemas/aptic-structure.schema.ts b/src/structures/schemas/aptic-structure.schema.ts new file mode 100644 index 0000000000000000000000000000000000000000..afb95430cb303041c529327f43d417c6d46792b3 --- /dev/null +++ b/src/structures/schemas/aptic-structure.schema.ts @@ -0,0 +1,43 @@ +export class ApticStructure { + presence_id: string; + + presence_name: string; + + presence_phone: string; + + presence_address: string; + + organization_id: string; + + organization_legal_status: string; + + organization_type: string; + + gps_lat: number; + + gps_lng: number; + + postal_code: string; + + city: string; + + city_lat: number; + + city_lng: number; + + department: string; + + department_code: string; + + region_name: string; + + region_code: string; + + catalog_id: string; + + service_count: number; + + created: string; + + updated: string; +} diff --git a/src/structures/schemas/structure.schema.ts b/src/structures/schemas/structure.schema.ts index 711c63714c5c39442ed74f8bf29d333788155755..b782161cbffe5c89fae607f26b8ed3756b77fc59 100644 --- a/src/structures/schemas/structure.schema.ts +++ b/src/structures/schemas/structure.schema.ts @@ -1,55 +1,43 @@ import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; -import { Document } from 'mongoose'; +import { Document, Types } from 'mongoose'; +import { Address } from './address.schema'; import { Week } from './week.schema'; export type StructureDocument = Structure & Document; -@Schema() +@Schema({ timestamps: { createdAt: 'createdAt', updatedAt: 'updatedAt' } }) export class Structure { - @Prop() - id: number; - @Prop() numero: string; @Prop() - dateDeCreation: string; - - @Prop() - derniereModification: string; + createdAt: string; @Prop() - nomDeLusager: string; + updatedAt: string; @Prop() - votreStructureEstElle: string; + structureName: string; @Prop() - nomDeVotreStructure: string; + structureType: string; @Prop() description: string; @Prop() - activitesMaintenuesDansLeCadreDuConfinement: string; - - @Prop() - n: string; + lockdownActivity: string; @Prop() - voie: string; + address: Address; @Prop() - address: string; + contactPhone: string; @Prop() - telephone: string; - - @Prop() - courriel: string; - + contactMail: string; @Prop() - siteWeb: string; + website: string; @Prop() facebook: string; @@ -61,99 +49,83 @@ export class Structure { instagram: string; @Prop() - civilite: string; + linkedin: string; @Prop() - nom: string; + pmrAccess: boolean; @Prop() - prenom: string; + accessModality: string[]; @Prop() - fonction: string; + otherDescription: string; @Prop() - accessibilitePersonnesAMobiliteReduitePmr: string; + labelsQualifications: string[]; @Prop() - modalitesDacces: string[]; + publics: string[]; @Prop() - labelsEtQualifications: string[]; + exceptionalClosures: string; @Prop() - publicsAcceptes: string[]; + publicsAccompaniment: string[]; @Prop() - fermeturesExceptionnelles: string; + proceduresAccompaniment: string[]; @Prop() - jaccompagneLesUsagersDansLeursDemarchesEnLigne: boolean; + baseSkills: string[]; @Prop() - accompagnementDesDemarches: string[]; + accessRight: string[]; @Prop() - autresAccompagnements: string; + socialAndProfessional: string[]; @Prop() - lesCompetencesDeBase: string[]; + parentingHelp: string[]; @Prop() - accesAuxDroits: string[]; + digitalCultureSecurity: string[]; @Prop() - insertionSocialeEtProfessionnelle: string[]; + equipmentsAndServices: string[]; @Prop() - aideALaParentalite: string[]; + freeWifi: boolean; @Prop() - cultureEtSecuriteNumerique: string[]; - - @Prop() - equipementsEtServicesProposes: string[]; + freeWorkShop: boolean; @Prop() nbComputers: number; @Prop() - precisionsSiNecessaire: string; - - @Prop() - statutJuridique: string; - - @Prop() - appartenezVousAUnReseauDeMediation: string; - - @Prop() - precisezLequel: string; - - @Prop() - idDeLitemStructureDansDirectus: string; + nbPrinters: number; @Prop() - statutDeLitemStructureDansDirectus: string; + nbTablets: number; @Prop() - idDeLitemOffreDansDirectus: string; + nbNumericTerminal: number; @Prop() - statut: string; + nbScanners: number; @Prop() - typeDeStructure: string[]; + hours: Week; @Prop() - commune: string; + coord: number[]; @Prop() - hours: Week; + deletedAt: Date; @Prop() - coord: number[]; + accountVerified: boolean; } export const StructureSchema = SchemaFactory.createForClass(Structure); - StructureSchema.index({ '$**': 'text' }); diff --git a/src/structures/services/aptic-structures.service.ts b/src/structures/services/aptic-structures.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..f2bd9fdc20670bad14a289eb1a29ddaff6f7cc26 --- /dev/null +++ b/src/structures/services/aptic-structures.service.ts @@ -0,0 +1,159 @@ +import { HttpService, Injectable } from '@nestjs/common'; +import { Logger } from '@nestjs/common'; +import { Observable } from 'rxjs'; +import { AxiosResponse } from 'axios'; +import { Cron, CronExpression } from '@nestjs/schedule'; +import * as _ from 'lodash'; +import * as https from 'https'; +import { InjectModel } from '@nestjs/mongoose'; +import { Model } from 'mongoose'; +import { Structure, StructureDocument } from '../schemas/structure.schema'; +import { ApticStructure } from '../schemas/aptic-structure.schema'; +import { Address } from '../schemas/address.schema'; +import { UsersService } from '../../users/users.service'; + +@Injectable() +export class ApticStructuresService { + constructor( + private readonly httpService: HttpService, + private readonly userService: UsersService, + @InjectModel(Structure.name) private structureModel: Model<StructureDocument> + ) {} + + public formatApticStructures(postalCodeData: any[]): any { + // Get all postal code in one array + const postalCodeArray = _.flatten( + postalCodeData.map((data) => { + return data.codesPostaux; + }) + ); + + // Call APTIC Api's + const postalCodePromises = postalCodeArray.map((postalCode) => { + return this.getApticStructures(postalCode).toPromise(); + }); + + Promise.all(postalCodePromises).then((data) => { + const structuresData = _.flatten( + data.map((tmp: { data }) => { + return tmp.data.data; + }) + ); + // Create structures if possible + structuresData.forEach((structure) => this.createApticStructures(structure)); + }); + } + + private async createApticStructures(structure: ApticStructure): Promise<any> { + this.structureAlreadyExist(structure).then((exist) => { + if (!exist) { + Logger.log(`Create structure : ${structure.presence_name}`, 'ApticStructuresService - createApticStructures'); + const createdStructure = new this.structureModel(); + createdStructure.coord = [structure.gps_lng, structure.gps_lat]; + createdStructure.structureName = structure.presence_name; + createdStructure.contactPhone = structure.presence_phone; + createdStructure.labelsQualifications = ['passNumerique']; + // Address + createdStructure.address = this.formatAddress(structure); + createdStructure.save(); + // Send admin weird structure mail + this.verifyDuplication(createdStructure); + } + }); + } + + private async structureAlreadyExist(structure: ApticStructure): Promise<boolean> { + let existingStructure = await this.structureModel + .findOne({ + structureName: { $regex: structure.presence_name, $options: 'i' }, + }) + .exec(); + // Check without regex for case like 'TINEBRA*DANIEL/DANIEL/' + if (!existingStructure) { + existingStructure = await this.structureModel.findOne({ structureName: structure.presence_name }).exec(); + } + + if (existingStructure) { + // Add aptic label if it's not the case + if (!existingStructure.labelsQualifications.includes('passNumerique')) { + existingStructure.labelsQualifications.push('passNumerique'); + existingStructure.save(); + } + return true; + } + return false; + } + + /** + * Get Metropole new aptic structure evey week. For testing, please change the expression + */ + @Cron(CronExpression.EVERY_WEEK) + public getMetopoleMunicipality(): void { + const req = + 'https://download.data.grandlyon.com/ws/grandlyon/adr_voie_lieu.adrcomgl/all.json?maxfeatures=-1&start=1'; + Logger.log(`Request : ${req}`, 'ApticStructuresService - getMetopoleMunicipality'); + this.httpService.get(encodeURI(req)).subscribe( + (data) => { + const inseeArray = data.data.values.map((municipality) => { + return this.getPostalCodeWithINSEE(municipality.insee).toPromise(); + }); + Promise.all(inseeArray).then((inseData) => { + const postalCodeArray = inseData.map((cpData: { data; config }) => { + return cpData.data; + }); + this.formatApticStructures(postalCodeArray); + }); + }, + (err) => Logger.error(err) + ); + } + + public getPostalCodeWithINSEE(inseeCode: string): Observable<AxiosResponse<any>> { + const req = `https://geo.api.gouv.fr/communes/${inseeCode}?fields=codesPostaux&format=json`; + Logger.log(`Request : ${req}`, 'ApticStructuresService - getMetopoleMunicipality'); + return this.httpService.get(encodeURI(req)); + } + + public getApticStructures(postalCodeData: string): Observable<AxiosResponse<any>> { + const agent = new https.Agent({ + rejectUnauthorized: false, + }); + const req = `https://presence.aptic.fr/postal_code/${postalCodeData}`; + Logger.log(`Request : ${req}`, 'ApticStructuresService'); + return this.httpService.get(req, { + httpsAgent: agent, + headers: { + api_key: process.env.APTIC_TOKEN, + }, + }); + } + + private async verifyDuplication(createdStructure: StructureDocument): Promise<void> { + const sameAddrStructure = await this.structureModel + .findOne({ + _id: { $ne: createdStructure._id }, + address: createdStructure.address, + }) + .exec(); + if (sameAddrStructure) { + this.userService.sendAdminApticStructureMail(createdStructure.structureName, sameAddrStructure.structureName); + } + } + + /** + * Format aptic structure address + */ + private formatAddress(structure: ApticStructure): Address { + const address = new Address(); + const regexWithSpace = /\d+\s/g; // NOSONAR + const regex = /\d+/g; // NOSONAR + if (structure.presence_address.match(regex)) { + address.numero = structure.presence_address.match(regex)[0]; + address.street = structure.presence_address.replace(regexWithSpace, ''); + } else { + address.street = structure.presence_address; + } + address.commune = structure.city; + return address; + } +} diff --git a/src/structures/services/structures.service.ts b/src/structures/services/structures.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..f574d12fdf0a4c5369b33d0a379a79c2d68bd682 --- /dev/null +++ b/src/structures/services/structures.service.ts @@ -0,0 +1,312 @@ +import { HttpException, HttpService, Injectable, HttpStatus, Logger } from '@nestjs/common'; +import { InjectModel } from '@nestjs/mongoose'; +import { Types, Model } from 'mongoose'; +import { Observable } from 'rxjs'; +import { AxiosResponse } from 'axios'; +import { Structure, StructureDocument } from '../schemas/structure.schema'; +import * as ejs from 'ejs'; +import { structureDto } from '../dto/structure.dto'; +import { UsersService } from '../../users/users.service'; +import { User } from '../../users/schemas/user.schema'; +import { MailerService } from '../../mailer/mailer.service'; +import { Cron, CronExpression } from '@nestjs/schedule'; +import { DateTime } from 'luxon'; + +@Injectable() +export class StructuresService { + constructor( + private readonly httpService: HttpService, + private readonly userService: UsersService, + private readonly mailerService: MailerService, + @InjectModel(Structure.name) private structureModel: Model<StructureDocument> + ) {} + + public async create(idUser: string, structureDto: structureDto): Promise<Structure> { + const user = await this.userService.findOne(idUser); + if (!user) { + throw new HttpException('Invalid profile', HttpStatus.NOT_FOUND); + } + const createdStructure = new this.structureModel(structureDto); + createdStructure._id = Types.ObjectId(); + createdStructure.save(); + user.structuresLink.push(createdStructure._id); + user.save(); + + return createdStructure; + } + + public async search(searchString: string, filters?: Array<any>): Promise<Structure[]> { + if (searchString && filters) { + return this.structureModel + .find({ + $and: [ + ...this.parseFilter(filters), + { $text: { $search: searchString }, deletedAt: { $exists: false }, accountVerified: true }, + ], + }) + .exec(); + } else if (filters) { + return this.structureModel + .find({ $and: [{ $or: this.parseFilter(filters), deletedAt: { $exists: false }, accountVerified: true }] }) + .exec(); + } else { + return this.structureModel + .find({ + $and: [{ $or: [{ $text: { $search: searchString }, deletedAt: { $exists: false }, accountVerified: true }] }], + }) + .exec(); + } + } + + /** + * Parse filter value from string to boolean + * @param filters + */ + private parseFilter(filters: Array<any>): Array<any> { + return filters.map((filter) => { + const key = Object.keys(filter)[0]; + if (filter[key] === 'True') { + return { [key]: true }; + } else { + return filter; + } + }); + } + + public async findAll(): Promise<StructureDocument[]> { + const structures = await this.structureModel.find({ deletedAt: { $exists: false } }).exec(); + // Update structures coord and address before sending them + await Promise.all( + structures.map((structure: StructureDocument) => { + // If structre has no address, add it + if (!structure.address) { + return this.getStructurePosition(structure).then((postition) => { + this.structureModel + .findByIdAndUpdate(Types.ObjectId(structure._id), { address: postition.address, coord: postition.coord }) + .exec(); + }); + } + if (structure.coord.length <= 0) { + return new Promise((resolve) => { + this.getStructurePosition(structure).then((postition: StructureDocument) => { + this.structureModel + .findByIdAndUpdate(Types.ObjectId(postition._id), { coord: postition.coord }) + .exec() + .then(() => { + resolve(''); + }); + }); + }); + } + }) + ); + return this.structureModel.find({ deletedAt: { $exists: false }, accountVerified: true }).exec(); + } + + public async update(idStructure: string, structure: structureDto): Promise<Structure> { + const result = await this.structureModel.findByIdAndUpdate(Types.ObjectId(idStructure), structure).exec(); + if (!result) { + throw new HttpException('Invalid structure id', HttpStatus.BAD_REQUEST); + } else { + this.userService.removeOutdatedStructureFromArray(idStructure); + } + return this.findOne(idStructure); + } + + public async findOne(idParam: string): Promise<Structure> { + return await this.structureModel.findById(Types.ObjectId(idParam)).exec(); + } + /** + * Get structures positions and add marker corresponding to those positons on the map + */ + private getStructurePosition(structure: Structure): Promise<Structure> { + return new Promise((resolve) => { + this.getCoord(structure.address.numero, structure.address.street, structure.address.commune).subscribe( + (res) => { + const address = res.data.features[0]; + structure.coord = address.geometry.coordinates; + resolve(structure); + }, + (err) => { + Logger.error(`Request error: ${err.config.url}`, 'StructureService'); + Logger.error(err); + } + ); + }); + } + + public async isClaimed(structureId: string, user: User): Promise<boolean> { + const isStructureClaimed = await this.userService.isStructureClaimed(structureId); + const isUserAlreadyClaimed = await this.userService.isUserAlreadyClaimedStructure(structureId, user.email); + if (isStructureClaimed || isUserAlreadyClaimed) { + return true; + } + return false; + } + + /** + * Search structure address based on data search WS + */ + public async searchAddress(data: { searchQuery: string }): Promise<AxiosResponse<any>> { + const req = 'https://data.grandlyon.com/api/elasticsearch/_search'; + const queryString = data.searchQuery.trim().replace(/\s/g, ' AND '); + const params = { + from: 0, + size: 30, + _source: ['data-fr'], + query: { + bool: { + filter: { + term: { + 'metadata-fr.geonet:info.uuid.keyword': '4cb035de-6ac3-4763-94d8-4c19b1d19607', + }, + }, + must: [ + { + query_string: { + query: queryString, + default_field: '*', + analyzer: 'my_search_analyzer', + fuzziness: 'AUTO', + minimum_should_match: '90%', + }, + }, + ], + }, + }, + }; + return new Promise((resolve, reject) => { + this.httpService + .request({ + url: req, + method: 'GET', + headers: { 'Content-Type': 'application/json' }, + data: params, + }) + .subscribe( + (reply) => { + return resolve(reply.data); + }, + (err) => { + Logger.error(`Request error: ${err.config.url}`, 'StructureService - search'); + Logger.error(err); + } + ); + }); + } + + /** + * Count every value occurence of a given key + * @param key structure key + * @return [{id: 'key', count: 'value'}] + */ + public async countByStructureKey(key: string): Promise<any> { + const uniqueElements = await this.structureModel.distinct(key).exec(); + return await Promise.all( + uniqueElements.map(async (value) => { + return { + id: value, + count: await this.structureModel + .countDocuments({ $and: [{ [key]: { $elemMatch: { $eq: value } }, deletedAt: { $exists: false } }] }) + .exec(), + }; + }) + ); + } + + public getCoord(numero: string, address: string, zipcode: string): Observable<AxiosResponse<any>> { + const req = + 'https://download.data.grandlyon.com/geocoding/photon/api' + '?q=' + numero + ' ' + address + ' ' + zipcode; + Logger.log(`Request : ${req}`, 'StructureService - getCoord'); + return this.httpService.get(encodeURI(req)); + } + + public async deleteOne(id: string): Promise<Structure> { + const structure = await this.structureModel.findById(Types.ObjectId(id)).exec(); + if (!structure) { + throw new HttpException('Invalid structure id', HttpStatus.BAD_REQUEST); + } + structure.deletedAt = DateTime.local().setZone('Europe/Paris').toString(); + this.anonymizeStructure(structure).save(); + return structure; + } + + private anonymizeStructure(structure: StructureDocument): StructureDocument { + structure.contactPhone = ''; + structure.contactMail = ''; + structure.facebook = ''; + structure.twitter = ''; + structure.instagram = ''; + structure.website = ''; + return structure; + } + + @Cron(CronExpression.EVERY_DAY_AT_4AM) + public async checkOutdatedStructuresInfo(): Promise<void> { + const OUTDATED_MONT_TO_CHECK = 6; + const structureList = await this.findAll(); + const local = DateTime.local().setZone('Europe/Paris'); + // Get outdated structures + const filteredList = structureList.filter((structure) => { + const updateDate = DateTime.fromFormat(structure.updatedAt.split(' GMT')[0], 'EEE MMM dd yyyy HH:mm:ss'); + const diff = local.diff(updateDate, ['months']); + if (diff.values.months > OUTDATED_MONT_TO_CHECK) { + return true; + } + }); + // Get owners of outdated structures + const ownerList = await Promise.all( + filteredList.map(async (structure) => { + const owner = await this.userService.isStructureClaimed(structure._id.toString()); + if (owner) { + return { structure: structure, owner: owner }; + } + }) + ); + // Send email if possible and update user + ownerList + .filter((x) => x != undefined) + .forEach(async (data) => { + this.userService.findOne(data.owner.email).then((user) => { + // If mail is already sent, do not resend + if (user.structureOutdatedMailSent.includes(data.structure._id)) { + return; + } else { + this.sendOutdatedEmailToUser(data.owner.email, data.structure.structureName, data.structure._id); + user.structureOutdatedMailSent.push(data.structure._id); + user.save(); + } + }); + }); + } + + /** + * Generate activation token and send it to user by email, in order to validate + * a new account. + * @param user User + */ + private async sendOutdatedEmailToUser(userEmail: string, structureName: string, id: string): Promise<any> { + const config = this.mailerService.config; + const ejsPath = this.mailerService.getTemplateLocation(config.templates.structureOutdatedInfo.ejs); + const jsonConfig = this.mailerService.loadJsonConfig(config.templates.structureOutdatedInfo.json); + + const html = await ejs.renderFile(ejsPath, { + config, + name: structureName, + id: id, + }); + this.mailerService.send(userEmail, jsonConfig.subject, html); + } + + public async updateAccountVerified(idStructure: string, emailUser: string): Promise<Structure> { + const user = await this.userService.findOne(emailUser); + const structureLinked = await this.findOne(user.structuresLink[0].toHexString()); + const structure = new this.structureModel(structureLinked); + if (!structure) { + throw new HttpException('Invalid structure', HttpStatus.NOT_FOUND); + } + structure.accountVerified = true; + structure.save(); + return structure; + } +} diff --git a/src/structures/structure-type/structure-type.controller.ts b/src/structures/structure-type/structure-type.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..2e530a5dd77c3dea93a14ea3e5b3db3abd1b65ed --- /dev/null +++ b/src/structures/structure-type/structure-type.controller.ts @@ -0,0 +1,13 @@ +import { Controller, Get } from '@nestjs/common'; +import { StructureType } from './structure-type.schema'; +import { StructureTypeService } from './structure-type.service'; + +@Controller('structure-type') +export class StructureTypeController { + constructor(private readonly structureTypeService: StructureTypeService) {} + + @Get() + public async findAll(): Promise<StructureType[]> { + return this.structureTypeService.findAll(); + } +} diff --git a/src/structures/structure-type/structure-type.dto.ts b/src/structures/structure-type/structure-type.dto.ts new file mode 100644 index 0000000000000000000000000000000000000000..0064714bcd64ca312577d6a549c601c607727d83 --- /dev/null +++ b/src/structures/structure-type/structure-type.dto.ts @@ -0,0 +1,4 @@ +export class CreateStructureType { + name: string; + values: string[]; +} diff --git a/src/structures/structure-type/structure-type.schema.ts b/src/structures/structure-type/structure-type.schema.ts new file mode 100644 index 0000000000000000000000000000000000000000..faa2ecbb6014b6aa176125a686602a4a09fd9957 --- /dev/null +++ b/src/structures/structure-type/structure-type.schema.ts @@ -0,0 +1,15 @@ +import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; +import { Document } from 'mongoose'; + +export type StructureTypeDocument = StructureType & Document; + +@Schema({ collection: 'structuretype' }) +export class StructureType { + @Prop() + name: string; + + @Prop() + values: string[]; +} + +export const StructureTypeSchema = SchemaFactory.createForClass(StructureType); diff --git a/src/structures/structure-type/structure-type.service.ts b/src/structures/structure-type/structure-type.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..ea50e0c79e697e9a07ca28f9ebdbf950608835bf --- /dev/null +++ b/src/structures/structure-type/structure-type.service.ts @@ -0,0 +1,13 @@ +import { Injectable } from '@nestjs/common'; +import { InjectModel } from '@nestjs/mongoose'; +import { Model } from 'mongoose'; +import { StructureType, StructureTypeDocument } from './structure-type.schema'; + +@Injectable() +export class StructureTypeService { + constructor(@InjectModel(StructureType.name) private structureTypeModel: Model<StructureTypeDocument>) {} + + public async findAll(): Promise<StructureType[]> { + return this.structureTypeModel.find().exec(); + } +} diff --git a/src/structures/structures.controller.spec.ts b/src/structures/structures.controller.spec.ts deleted file mode 100644 index f8fecbb2c5ddee4074370e863435d57e821110c3..0000000000000000000000000000000000000000 --- a/src/structures/structures.controller.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { StructuresController } from './structures.controller'; - -describe('StructuresController', () => { - let controller: StructuresController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [StructuresController], - }).compile(); - - controller = module.get<StructuresController>(StructuresController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/src/structures/structures.controller.ts b/src/structures/structures.controller.ts index b3a33b8aa05b35c191abfef770bb228f2cb3a420..c3fb6ef641f799d45b46b427bc9fe26118f73313 100644 --- a/src/structures/structures.controller.ts +++ b/src/structures/structures.controller.ts @@ -1,16 +1,25 @@ -import { Body, Controller, Get, Param, Post, Query, Req } from '@nestjs/common'; +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Post, Put, Query, UseGuards } from '@nestjs/common'; +import { ApiParam } from '@nestjs/swagger'; +import { Types } from 'mongoose'; +import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; +import { Roles } from '../users/decorators/roles.decorator'; +import { IsStructureOwnerGuard } from '../users/guards/isStructureOwner.guard'; +import { RolesGuard } from '../users/guards/roles.guard'; +import { User } from '../users/schemas/user.schema'; +import { UsersService } from '../users/users.service'; import { CreateStructureDto } from './dto/create-structure.dto'; import { QueryStructure } from './dto/query-structure.dto'; +import { structureDto } from './dto/structure.dto'; import { Structure } from './schemas/structure.schema'; -import { StructuresService } from './structures.service'; +import { StructuresService } from './services/structures.service'; @Controller('structures') export class StructuresController { - constructor(private readonly structureService: StructuresService) {} + constructor(private readonly structureService: StructuresService, private readonly userService: UsersService) {} @Post() - public async create(@Body() createStructureDto: CreateStructureDto) { - await this.structureService.create(createStructureDto); + public async create(@Body() createStructureDto: CreateStructureDto): Promise<Structure> { + return this.structureService.create(createStructureDto.idUser, createStructureDto.structure); } @Post('search') @@ -18,26 +27,72 @@ export class StructuresController { return this.structureService.search(query.query, body ? body.filters : null); } + @Put('updateAfterOwnerVerify/:id') + public async updateAfterOwnerVerify( + @Param('id') id: string, + @Body() body: { emailUser: string } + ): Promise<Structure> { + return this.structureService.updateAccountVerified(id, body.emailUser); + } + + @Put(':id') + @UseGuards(JwtAuthGuard, IsStructureOwnerGuard) + @Roles('admin') + public async update(@Param('id') id: string, @Body() body: structureDto): Promise<Structure> { + return this.structureService.update(id, body); + } + @Get() public async findAll(): Promise<Structure[]> { return this.structureService.findAll(); } + @Post(':id/isClaimed') + public async isClaimed(@Param('id') id: string, @Body() user?: User): Promise<boolean> { + return this.structureService.isClaimed(id, user); + } + + @Post(':id/claim') + public async claim(@Param('id') idStructure: string, @Body() user: User): Promise<Types.ObjectId[]> { + return this.userService.updateStructureLinked(user.email, idStructure); + } + @Get('count') public async countCategories(): Promise<Array<{ id: string; count: number }>> { const data = await Promise.all([ - this.structureService.countByStructureKey('accesAuxDroits'), - this.structureService.countByStructureKey('aideALaParentalite'), - this.structureService.countByStructureKey('cultureEtSecuriteNumerique'), - this.structureService.countByStructureKey('insertionSocialeEtProfessionnelle'), - this.structureService.countByStructureKey('accompagnementDesDemarches'), - this.structureService.countByStructureKey('labelsEtQualifications'), - this.structureService.countByStructureKey('publicsAcceptes'), - this.structureService.countByStructureKey('modalitesDacces'), - this.structureService.countByStructureKey('lesCompetencesDeBase'), - this.structureService.countByStructureKey('equipementsEtServicesProposes'), + this.structureService.countByStructureKey('proceduresAccompaniment'), + + this.structureService.countByStructureKey('accessRight'), + this.structureService.countByStructureKey('baseSkills'), + this.structureService.countByStructureKey('parentingHelp'), + this.structureService.countByStructureKey('digitalCultureSecurity'), + this.structureService.countByStructureKey('socialAndProfessional'), + + this.structureService.countByStructureKey('publicsAccompaniment'), + this.structureService.countByStructureKey('labelsQualifications'), + this.structureService.countByStructureKey('publics'), + this.structureService.countByStructureKey('accessModality'), + this.structureService.countByStructureKey('equipmentsAndServices'), ]); // Return a concat of all arrays return data.reduce((a, b) => [...a, ...b]); } + + @Post('address') + public async searchAddress(@Body() data: { searchQuery: string }) { + return await this.structureService.searchAddress(data); + } + + @Get(':id') + public async find(@Param('id') id: string) { + return this.structureService.findOne(id); + } + + @Delete(':id') + @UseGuards(JwtAuthGuard, RolesGuard) + @Roles('admin') + @ApiParam({ name: 'id', type: String, required: true }) + public async delete(@Param('id') id: string) { + return this.structureService.deleteOne(id); + } } diff --git a/src/structures/structures.module.ts b/src/structures/structures.module.ts index 6a82aae92ddcec6e352b3ede79367694322e150f..6494ea3a5eed4386ed251679c6e5b45a66e88804 100644 --- a/src/structures/structures.module.ts +++ b/src/structures/structures.module.ts @@ -1,13 +1,27 @@ import { HttpModule, Module } from '@nestjs/common'; import { MongooseModule } from '@nestjs/mongoose'; import { MailerModule } from '../mailer/mailer.module'; +import { UsersModule } from '../users/users.module'; import { Structure, StructureSchema } from './schemas/structure.schema'; import { StructuresController } from './structures.controller'; -import { StructuresService } from './structures.service'; +import { StructuresService } from './services/structures.service'; +import { ApticStructuresService } from './services/aptic-structures.service'; +import { StructureTypeController } from './structure-type/structure-type.controller'; +import { StructureTypeService } from './structure-type/structure-type.service'; +import { StructureType, StructureTypeSchema } from './structure-type/structure-type.schema'; @Module({ - imports: [MongooseModule.forFeature([{ name: Structure.name, schema: StructureSchema }]), HttpModule, MailerModule], - controllers: [StructuresController], - providers: [StructuresService], + imports: [ + MongooseModule.forFeature([ + { name: Structure.name, schema: StructureSchema }, + { name: StructureType.name, schema: StructureTypeSchema }, + ]), + HttpModule, + MailerModule, + UsersModule, + ], + controllers: [StructuresController, StructureTypeController], + exports: [StructuresService, StructureTypeService], + providers: [StructuresService, StructureTypeService, ApticStructuresService], }) export class StructuresModule {} diff --git a/src/structures/structures.service.ts b/src/structures/structures.service.ts deleted file mode 100644 index 9e52b0937eee1a679ca545a70f2f1400c846a60a..0000000000000000000000000000000000000000 --- a/src/structures/structures.service.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { HttpService, Injectable } from '@nestjs/common'; -import { InjectModel } from '@nestjs/mongoose'; -import { Model } from 'mongoose'; -import { Observable } from 'rxjs'; -import { AxiosResponse } from 'axios'; -import { CreateStructureDto } from './dto/create-structure.dto'; -import { Structure, StructureDocument } from './schemas/structure.schema'; -import { Logger } from '@nestjs/common'; - -@Injectable() -export class StructuresService { - constructor( - private readonly httpService: HttpService, - @InjectModel(Structure.name) private structureModel: Model<StructureDocument> - ) {} - - public async create(createStructrureDto: CreateStructureDto): Promise<Structure> { - const createdStructure = new this.structureModel(createStructrureDto); - return createdStructure.save(); - } - - public async search(searchString: string, filters?: Array<any>): Promise<Structure[]> { - if (searchString && filters) { - return this.structureModel - .find({ $and: [...this.parseFilter(filters), { $text: { $search: searchString } }] }) - .exec(); - } else if (filters) { - return this.structureModel.find({ $or: this.parseFilter(filters) }).exec(); - } else { - return this.structureModel.find({ $or: [{ $text: { $search: searchString } }] }).exec(); - } - } - - /** - * Parse filter value from string to boolean - * @param filters - */ - private parseFilter(filters: Array<any>): Array<any> { - return filters.map((filter) => { - const key = Object.keys(filter)[0]; - if (filter[key] === 'True') { - return { [key]: true }; - } else { - return filter; - } - }); - } - - public async findAll(): Promise<Structure[]> { - const structures = await this.structureModel.find().exec(); - // Update structures coord and address before sending them - await Promise.all( - structures.map((structure: Structure) => { - // If structre has no address, add it - if (!structure.address) { - this.getStructurePosition(structure).then((postition) => { - this.structureModel - .findOneAndUpdate({ id: structure.id }, { address: postition.address, coord: postition.coord }) - .exec(); - }); - } - if (structure.coord.length <= 0) { - this.getStructurePosition(structure).then((postition) => { - this.structureModel.findOneAndUpdate({ id: postition.id }, { coord: postition.coord }).exec(); - }); - } - }) - ); - return this.structureModel.find().exec(); - } - - /** - * Get structures positions and add marker corresponding to those positons on the map - */ - private getStructurePosition(structure: Structure): Promise<Structure> { - return new Promise((resolve) => { - this.getCoord(structure.n, structure.voie, structure.commune).subscribe( - (res) => { - const address = res.data.features[0]; - structure.address = structure.voie + ' - ' + address.properties.postcode + ' ' + address.properties.city; - structure.coord = address.geometry.coordinates; - resolve(structure); - }, - (err) => { - Logger.error(`[getCoord] Request error: ${err.config.url}`, err); - } - ); - }); - } - - /** - * Count every value occurence of a given key - * @param key structure key - * @return [{id: 'key', count: 'value'}] - */ - public async countByStructureKey(key: string): Promise<any> { - const uniqueElements = await this.structureModel.distinct(key).exec(); - return await Promise.all( - uniqueElements.map(async (value) => { - return { - id: value, - count: await this.structureModel.countDocuments({ [key]: { $elemMatch: { $eq: value } } }).exec(), - }; - }) - ); - } - - public getCoord(numero: string, address: string, zipcode: string): Observable<AxiosResponse<any>> { - const req = - 'https://download.data.grandlyon.com/geocoding/photon/api' + '?q=' + numero + ' ' + address + ' ' + zipcode; - Logger.log(`[StructureService - getCoord] Request : ${req}`); - return this.httpService.get(encodeURI(req)); - } -} diff --git a/src/tcl/interfaces/pgis.coord.ts b/src/tcl/interfaces/pgis.coord.ts new file mode 100644 index 0000000000000000000000000000000000000000..61d354af70a9bb79fae1aedbb22bfd2ec52fccd6 --- /dev/null +++ b/src/tcl/interfaces/pgis.coord.ts @@ -0,0 +1,4 @@ +export interface PgisCoord { + type: string; + coordinates: [number, number]; +} diff --git a/src/tcl/tcl.module.ts b/src/tcl/tcl.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..f34de0b278a15d2ade2b6eaa2513ba23886026ea --- /dev/null +++ b/src/tcl/tcl.module.ts @@ -0,0 +1,12 @@ +import { HttpModule, Module } from '@nestjs/common'; +import { TclStopPointService } from './tclStopPoint.service'; +import { TclStopPointController } from './tclStopPoint.controller'; +import { MongooseModule } from '@nestjs/mongoose'; +import { TclStopPoint, TclStopPointSchema } from './tclStopPoint.schema'; + +@Module({ + imports: [MongooseModule.forFeature([{ name: TclStopPoint.name, schema: TclStopPointSchema }]), HttpModule], + providers: [TclStopPointService], + controllers: [TclStopPointController], +}) +export class TclModule {} diff --git a/src/tcl/tclStopPoint.controller.ts b/src/tcl/tclStopPoint.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..2f8b75148e41fd90186763ecec5958e4c2d8c388 --- /dev/null +++ b/src/tcl/tclStopPoint.controller.ts @@ -0,0 +1,35 @@ +import { Body, Controller, Get, Post, UseGuards } from '@nestjs/common'; +import { ApiOperation, ApiResponse } from '@nestjs/swagger'; +import { PgisCoord } from './interfaces/pgis.coord'; +import { TclStopPoint } from './tclStopPoint.schema'; +import { TclStopPointService } from './tclStopPoint.service'; + +@Controller('tcl') +export class TclStopPointController { + constructor(private tclStopPointService: TclStopPointService) {} + + @ApiOperation({ + description: `Mettre à jour les points d'arrêt TCL à partir de Data Grand Lyon`, + }) + @ApiResponse({ + status: 204, + description: 'The stop points have been updated successfully.', + }) + @Get('/update') + //TODO: protect with admin guard when available + public updateStopPoints(): Promise<void> { + return this.tclStopPointService.updateStopPoints(); + } + + @ApiOperation({ + description: `Récupérer les arrêts les plus proches d'un point géographique`, + }) + @ApiResponse({ + status: 200, + description: 'The closest stop points have been fetched successfully.', + }) + @Post('/closest') + public getClosestStopPoints(@Body() pgisCoord: PgisCoord): Promise<TclStopPoint[]> { + return this.tclStopPointService.getClosestStopPoints(pgisCoord); + } +} diff --git a/src/tcl/tclStopPoint.schema.ts b/src/tcl/tclStopPoint.schema.ts new file mode 100644 index 0000000000000000000000000000000000000000..acc072fa3925d225627e39b5f675c565ee192c3e --- /dev/null +++ b/src/tcl/tclStopPoint.schema.ts @@ -0,0 +1,50 @@ +import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; +import { Document } from 'mongoose'; + +export type TclStopPointDocument = TclStopPoint & Document; + +@Schema() +export class TclStopPoint { + @Prop() + id: number; + + @Prop() + name?: string; + + @Prop() + busLines?: string[]; + + @Prop() + subLines?: string[]; + + @Prop() + tramLines?: string[]; + + @Prop() + prm?: boolean; + + @Prop() + elevator?: boolean; + + @Prop() + escalator?: boolean; + + @Prop() + gid: number; + + @Prop() + lastUpdate?: Date; + + @Prop() + lastUpdateFme?: Date; + + @Prop() + pgisCoord?: string | any; + + @Prop() + distance?: number; +} + +export const TclStopPointSchema = SchemaFactory.createForClass(TclStopPoint); + +TclStopPointSchema.index({ pgisCoord: '2dsphere' }); diff --git a/src/tcl/tclStopPoint.service.spec.ts b/src/tcl/tclStopPoint.service.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..4958e59315416027ed003c26d79fbbe8d82fcbf6 --- /dev/null +++ b/src/tcl/tclStopPoint.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { TclStopPointService } from './tclStopPoint.service'; + +describe('TclService', () => { + let service: TclStopPointService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [TclStopPointService], + }).compile(); + + service = module.get<TclStopPointService>(TclStopPointService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/tcl/tclStopPoint.service.ts b/src/tcl/tclStopPoint.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..390ff30b18db65e405a2a51a48925da2f0a9f0a3 --- /dev/null +++ b/src/tcl/tclStopPoint.service.ts @@ -0,0 +1,293 @@ +import { HttpService, Injectable } from '@nestjs/common'; +import { InjectModel } from '@nestjs/mongoose'; +import { Model } from 'mongoose'; +import { PgisCoord } from './interfaces/pgis.coord'; +import { TclStopPoint, TclStopPointDocument } from './tclStopPoint.schema'; + +interface ReceivedStopPoint { + type: string; + properties: { + id: string; + nom: string; + desserte: string; + pmr: string; + ascenseur: string; + escalator: string; + gid: string; + last_update: string; + last_update_fme: string; + }; + geometry: PgisCoord; +} + +interface Lines { + busLines: string[]; + subLines: string[]; + tramLines: string[]; +} + +@Injectable() +export class TclStopPointService { + private receivedStopPoints: any[]; + private receivedBusLines: any[]; + private receivedSubLines: any[]; + private receivedTramLines: any[]; + + constructor( + private http: HttpService, + @InjectModel(TclStopPoint.name) private tclStopPointModel: Model<TclStopPointDocument> + ) {} + + /** + * Clear 'tclstoppoint' and fill it with data from Data Grand Lyon + */ + public async updateStopPoints(): Promise<void> { + await this.getUpdatedData(); + const newStopPoints = await this.processReceivedStopPoints(this.receivedStopPoints); + + this.tclStopPointModel.deleteMany({}, () => { + this.tclStopPointModel.insertMany(newStopPoints); + }); + } + + /** + * Get all tcl data from Data Grand Lyon + */ + private async getUpdatedData(): Promise<void> { + this.receivedStopPoints = await this.http + .get( + // tslint:disable-next-line: max-line-length + 'https://download.data.grandlyon.com/wfs/rdata?SERVICE=WFS&VERSION=2.0.0&request=GetFeature&typename=tcl_sytral.tclarret&outputFormat=application/json; subtype=geojson&SRSNAME=EPSG:4326&startIndex=0' + ) + .toPromise() + .then(async (res) => res.data.features); + + this.receivedBusLines = await this.http + .get(`https://download.data.grandlyon.com/ws/rdata/tcl_sytral.tcllignebus_2_0_0/all.json`) + .toPromise() + .then(async (res) => res.data.values); + + this.receivedSubLines = await this.http + .get(`https://download.data.grandlyon.com/ws/rdata/tcl_sytral.tcllignemf_2_0_0/all.json`) + .toPromise() + .then(async (res) => res.data.values); + + this.receivedTramLines = await this.http + .get(`https://download.data.grandlyon.com/ws/rdata/tcl_sytral.tcllignetram_2_0_0/all.json`) + .toPromise() + .then(async (res) => res.data.values); + } + + /** + * Get all lines names and remove duplications + */ + private async processReceivedStopPoints(receivedStopPoints: ReceivedStopPoint[]): Promise<TclStopPoint[]> { + const newStopPoints: TclStopPoint[] = []; + + for (const receivedStopPoint of receivedStopPoints) { + const lines: Lines = await this.processReceivedLines(receivedStopPoint.properties.desserte); + + const newStopPoint = new TclStopPoint(); + newStopPoint.id = parseInt(receivedStopPoint.properties.id, 10); + newStopPoint.name = receivedStopPoint.properties.nom; + newStopPoint.busLines = [...new Set(lines.busLines)]; + newStopPoint.subLines = lines.subLines; + newStopPoint.tramLines = lines.tramLines; + newStopPoint.prm = JSON.parse(receivedStopPoint.properties.pmr); + newStopPoint.elevator = JSON.parse(receivedStopPoint.properties.ascenseur); + newStopPoint.escalator = JSON.parse(receivedStopPoint.properties.escalator); + newStopPoint.gid = parseInt(receivedStopPoint.properties.gid, 10); + newStopPoint.lastUpdate = new Date(receivedStopPoint.properties.last_update); + newStopPoint.lastUpdateFme = new Date(receivedStopPoint.properties.last_update_fme); + newStopPoint.pgisCoord = receivedStopPoint.geometry; + + newStopPoints.push(newStopPoint); + } + + return newStopPoints; + } + + /** + * Based on received data, check type and get it's real name in order to sort it. + */ + private async processReceivedLines(receivedLines: string): Promise<Lines> { + const receivedLinesArray = receivedLines.split(','); + const lines: Lines = { + busLines: [], + subLines: [], + tramLines: [], + }; + + for (let line of receivedLinesArray) { + line = line.split(':')[0]; + let cleanLine: string; + let lineType: string[]; + + if (this.isExceptionLine(line)) { + // Ne rien faire + } else if (this.isSubLine(line)) { + cleanLine = await this.getCleanSubLine(line); + lineType = lines.subLines; + } else if (this.isTramLine(line)) { + cleanLine = await this.getCleanTramLine(line); + lineType = lines.tramLines; + } else { + /* Les codes des lignes de bus ne respectant pas de logique générale, + on considère que toutes les lignes qui n'ont pas été interceptées au dessus + sont des lignes de bus */ + cleanLine = await this.getCleanBusLine(line); + lineType = lines.busLines; + } + + if (cleanLine) { + lineType.push(cleanLine); + } + } + + return lines; + } + + /** + * Return true if bus line code is : XXX11 + */ + private isSubLine(line: string): boolean { + const regex = /^3\d{2}/; // NOSONAR + return regex.test(line); + } + + /** + * Return true if bus line code is starting with a T + */ + private isTramLine(line: string): boolean { + const regex = /^T/; // NOSONAR + return regex.test(line); + } + + /** + * Return true if it's a known exception (ex: Rhônexpress) + */ + private isExceptionLine(line: string): boolean { + const regex = /(^RX|^TGS|^BGS|^NAV)/; // NOSONAR + return regex.test(line); + } + + /** + * Get back bus line name from TCL code in the corresponding table + */ + private async getCleanLine(line: string, receivedLines: any[]): Promise<string> { + const foundLine = receivedLines.find((receivedLine) => receivedLine.code_ligne === line); + + // Exception for line 132. Does'nt exist anymore + if (foundLine && foundLine.ligne && foundLine.ligne !== '132') { + return foundLine.ligne; + } else { + return ''; + } + } + + /** + * Get back bus line name from TCL code + */ + private async getCleanBusLine(line: string): Promise<string> { + return this.getCleanLine(line, this.receivedBusLines); + } + + /** + * Get back bus subway name from TCL code + */ + private async getCleanSubLine(line: string): Promise<string> { + return this.getCleanLine(line, this.receivedSubLines); + } + + /** + * Get back tram line name from TCL code + */ + private async getCleanTramLine(line: string): Promise<string> { + return this.getCleanLine(line, this.receivedTramLines); + } + + /** + * Get TCL nearast point. + * If none is found, we increase the default search radius. + * The amount of stop return if defined in the function. + */ + public async getClosestStopPoints(pgisCoord: PgisCoord): Promise<TclStopPoint[]> { + const NUMBER_STOPS = 5; + const RADIUS_FIRST_TRY = 100; + const RADIUS_SECOND_TRY = 500; + + let stopPoints = await this.getStopPointsByDistance(pgisCoord, RADIUS_FIRST_TRY); + + if (!stopPoints.length) { + stopPoints = await this.getStopPointsByDistance(pgisCoord, RADIUS_SECOND_TRY); + } + + stopPoints = this.groupStopPointsByName(stopPoints); + return stopPoints.slice(0, NUMBER_STOPS); + } + + /** + * Aggregate stops + */ + private groupStopPointsByName(stopPoints: TclStopPoint[]): TclStopPoint[] { + const uniqueStopPoints: TclStopPoint[] = []; + + for (const stopPoint of stopPoints) { + const stopPointIndex = uniqueStopPoints.findIndex((uniqueStopPoint) => uniqueStopPoint.name === stopPoint.name); + + if (stopPointIndex > -1) { + uniqueStopPoints[stopPointIndex].busLines = this.getUniqueCombinedLines( + uniqueStopPoints[stopPointIndex].busLines, + stopPoint.busLines + ); + + uniqueStopPoints[stopPointIndex].subLines = this.getUniqueCombinedLines( + uniqueStopPoints[stopPointIndex].subLines, + stopPoint.subLines + ); + + uniqueStopPoints[stopPointIndex].tramLines = this.getUniqueCombinedLines( + uniqueStopPoints[stopPointIndex].tramLines, + stopPoint.tramLines + ); + } else { + uniqueStopPoints.push(stopPoint); + } + } + + return uniqueStopPoints; + } + + /** + * Merge two lines array and return a map of unique lines ordered alphabetically + */ + private getUniqueCombinedLines(stop1Lines: string[], stop2Lines: string[]): string[] { + // Natural line order + // Ex : 69, 296, C7, C25 instead of 296, 69, C25, C7 + const collator = new Intl.Collator(undefined, { + numeric: true, + sensitivity: 'base', + }); + + return Array.from(new Set([...stop1Lines, ...stop2Lines])).sort(collator.compare); + } + + /** + * Query collection to get neareast coord + * @param pgisCoord PgisCoord + * @param maxDistance number + */ + public async getStopPointsByDistance(pgisCoord: PgisCoord, maxDistance: number): Promise<TclStopPoint[]> { + return this.tclStopPointModel + .find({ + pgisCoord: { + $near: { + $geometry: pgisCoord, + $maxDistance: maxDistance, + }, + }, + }) + .sort('-distance') + .exec(); + } +} diff --git a/src/users/decorators/roles.decorator.ts b/src/users/decorators/roles.decorator.ts new file mode 100644 index 0000000000000000000000000000000000000000..b0376727cc30742bda0e26d1e498c4f36fd4be02 --- /dev/null +++ b/src/users/decorators/roles.decorator.ts @@ -0,0 +1,3 @@ +import { SetMetadata } from '@nestjs/common'; + +export const Roles = (...roles: string[]) => SetMetadata('roles', roles); diff --git a/src/users/dto/change-email.dto.ts b/src/users/dto/change-email.dto.ts new file mode 100644 index 0000000000000000000000000000000000000000..eb783e4490f15c9c32b2f78d4f6dae4f679553fd --- /dev/null +++ b/src/users/dto/change-email.dto.ts @@ -0,0 +1,10 @@ +import { IsEmail, IsNotEmpty } from 'class-validator'; + +export class EmailChangeDto { + @IsNotEmpty() + @IsEmail() + readonly newEmail: string; + @IsNotEmpty() + @IsEmail() + readonly oldEmail: string; +} diff --git a/src/users/dto/change-password.dto.ts b/src/users/dto/change-password.dto.ts new file mode 100644 index 0000000000000000000000000000000000000000..73e54f82c21801158042fc060d76ed969e20d5c1 --- /dev/null +++ b/src/users/dto/change-password.dto.ts @@ -0,0 +1,14 @@ +import { IsNotEmpty, IsString } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger'; + +export class PasswordChangeDto { + @ApiProperty({ type: String }) + @IsNotEmpty() + @IsString() + readonly newPassword: string; + + @ApiProperty({ type: String }) + @IsNotEmpty() + @IsString() + readonly oldPassword: string; +} diff --git a/src/users/dto/create-user.dto.ts b/src/users/dto/create-user.dto.ts new file mode 100644 index 0000000000000000000000000000000000000000..db7bd07018e865772b44ffae7b5e8dd153529bfa --- /dev/null +++ b/src/users/dto/create-user.dto.ts @@ -0,0 +1,30 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator'; + +export class CreateUserDto { + @ApiProperty({ type: String }) + @IsNotEmpty() + @IsString() + readonly password: string; + + @IsNotEmpty() + @IsEmail() + @ApiProperty({ type: String }) + email: string; + + @IsNotEmpty() + @ApiProperty({ type: String }) + name: string; + + @IsNotEmpty() + @ApiProperty({ type: String }) + surname: string; + + @IsNotEmpty() + @ApiProperty({ type: String }) + phone: string; + + @IsArray() + @IsOptional() + pendingStructuresLink?: Array<number>; +} diff --git a/src/users/dto/reset-password-apply.dto.ts b/src/users/dto/reset-password-apply.dto.ts new file mode 100644 index 0000000000000000000000000000000000000000..7fd1ead4fe52ed3831fb8883b6fd3cbe597b8099 --- /dev/null +++ b/src/users/dto/reset-password-apply.dto.ts @@ -0,0 +1,13 @@ +import { IsNotEmpty, IsString } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger'; + +export class PasswordResetApplyDto { + @ApiProperty({ type: String }) + @IsNotEmpty() + @IsString() + readonly password: string; + @ApiProperty({ type: String }) + @IsNotEmpty() + @IsString() + readonly token: string; +} diff --git a/src/users/dto/reset-password.dto.ts b/src/users/dto/reset-password.dto.ts new file mode 100644 index 0000000000000000000000000000000000000000..1f3e8e1b14e784f835316063e35627e07559fc7b --- /dev/null +++ b/src/users/dto/reset-password.dto.ts @@ -0,0 +1,9 @@ +import { IsNotEmpty, IsEmail } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger'; + +export class PasswordResetDto { + @ApiProperty({ type: String, example: 'toto@mii.com' }) + @IsNotEmpty() + @IsEmail() + readonly email: string; +} diff --git a/src/users/enum/user-role.enum.ts b/src/users/enum/user-role.enum.ts new file mode 100644 index 0000000000000000000000000000000000000000..b9d5914503724d46f5b476c6b55dd39498b4c3e4 --- /dev/null +++ b/src/users/enum/user-role.enum.ts @@ -0,0 +1,4 @@ +export enum UserRole { + user, + admin, +} diff --git a/src/users/guards/isStructureOwner.guard.ts b/src/users/guards/isStructureOwner.guard.ts new file mode 100644 index 0000000000000000000000000000000000000000..5c1de520b86f92083ac3eadc89c755e5c91bcb58 --- /dev/null +++ b/src/users/guards/isStructureOwner.guard.ts @@ -0,0 +1,21 @@ +import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { User } from '../schemas/user.schema'; +import { RolesGuard } from './roles.guard'; + +@Injectable() +export class IsStructureOwnerGuard extends RolesGuard implements CanActivate { + constructor(protected readonly reflector: Reflector) { + super(reflector); + } + + canActivate(context: ExecutionContext): boolean { + const req = context.switchToHttp().getRequest(); + const user: User = req.user; + const idStructure = req.params.id; + if (user.structuresLink.includes(idStructure)) { + return true; + } + return super.canActivate(context); + } +} diff --git a/src/users/guards/roles.guard.ts b/src/users/guards/roles.guard.ts new file mode 100644 index 0000000000000000000000000000000000000000..fc06b77adf1253eb6efaf51fedfcc1c71a106882 --- /dev/null +++ b/src/users/guards/roles.guard.ts @@ -0,0 +1,33 @@ +import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { UserRole } from '../enum/user-role.enum'; + +@Injectable() +export class RolesGuard implements CanActivate { + constructor(protected reflector: Reflector) {} + + canActivate(context: ExecutionContext): boolean { + const roles = this.reflector.get<string[]>('roles', context.getHandler()); + if (!roles) { + return true; + } + const request = context.switchToHttp().getRequest(); + const user = request.user; + return this.matchRoles(user.role, roles[0]); + } + + /** + * Return true if user is admin or if the requested role match the user role. + * @param userRole user role from request + * @param expectedRole role requested by the endpoint + */ + private matchRoles(userRole: number, expectedRole: string): boolean { + if (userRole === UserRole.admin) { + return true; + } else if (userRole === UserRole[expectedRole]) { + return true; + } else { + return false; + } + } +} diff --git a/src/users/interfaces/user.interface.ts b/src/users/interfaces/user.interface.ts new file mode 100644 index 0000000000000000000000000000000000000000..683a069b82d162e1116318e825af966b4b89ca28 --- /dev/null +++ b/src/users/interfaces/user.interface.ts @@ -0,0 +1,19 @@ +import { Document, Types } from 'mongoose'; + +export interface IUser extends Document { + readonly _id: string; + email: string; + name: string; + surname: string; + phone: string; + password: string; + emailVerified: boolean; + validationToken: string; + resetPasswordToken: string; + role: number; + changeEmailToken: string; + newEmail: string; + structuresLink: Types.ObjectId[]; + pendingStructuresLink: Types.ObjectId[]; + structureOutdatedMailSent: Types.ObjectId[]; +} diff --git a/src/users/schemas/user.schema.ts b/src/users/schemas/user.schema.ts new file mode 100644 index 0000000000000000000000000000000000000000..a540db0d6ff77fe751d942e9b4980951d29e0ab2 --- /dev/null +++ b/src/users/schemas/user.schema.ts @@ -0,0 +1,49 @@ +import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; +import { Types } from 'mongoose'; +import { UserRole } from '../enum/user-role.enum'; +@Schema() +export class User { + @Prop({ required: true }) + email: string; + + @Prop({ required: true }) + name: string; + + @Prop({ required: true }) + surname: string; + + @Prop({ required: true }) + phone: string; + + @Prop({ required: true }) + password: string; + + @Prop({ default: false }) + emailVerified: boolean; + + @Prop({ default: null }) + validationToken: string; + + @Prop({ default: null }) + resetPasswordToken: string; + + @Prop({ enum: [UserRole.admin, UserRole.user], default: UserRole.user }) + role: number; + + @Prop({ default: null }) + changeEmailToken: string; + + @Prop({ default: null }) + newEmail: string; + + @Prop({ default: null }) + structuresLink: Types.ObjectId[]; + + @Prop({ default: null }) + pendingStructuresLink: Types.ObjectId[]; + + @Prop({ default: null }) + structureOutdatedMailSent: Types.ObjectId[]; +} + +export const UserSchema = SchemaFactory.createForClass(User); diff --git a/src/users/users.controller.spec.ts b/src/users/users.controller.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..7dd57ee72fcb1a6243abc0c94b7351825843f492 --- /dev/null +++ b/src/users/users.controller.spec.ts @@ -0,0 +1,33 @@ +import { HttpModule } from '@nestjs/common'; +import { getModelToken } from '@nestjs/mongoose'; +import { Test, TestingModule } from '@nestjs/testing'; +import { ConfigurationModule } from '../configuration/configuration.module'; +import { MailerService } from '../mailer/mailer.service'; +import { User } from './schemas/user.schema'; +import { UsersController } from './users.controller'; +import { UsersService } from './users.service'; + +describe('UsersController', () => { + let controller: UsersController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [ConfigurationModule, HttpModule], + providers: [ + UsersService, + MailerService, + { + provide: getModelToken('User'), + useValue: User, + }, + ], + controllers: [UsersController], + }).compile(); + + controller = module.get<UsersController>(UsersController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..43fb564f56211d4f7fd7b0952d9237f8308d3bb4 --- /dev/null +++ b/src/users/users.controller.ts @@ -0,0 +1,89 @@ +import { Body, Controller, Get, Param, Post, Query, Request, UseGuards } from '@nestjs/common'; +import { ApiBearerAuth, ApiOperation, ApiParam, ApiResponse } from '@nestjs/swagger'; +import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; +import { PasswordChangeDto } from './dto/change-password.dto'; +import { EmailChangeDto } from './dto/change-email.dto'; +import { CreateUserDto } from './dto/create-user.dto'; +import { PasswordResetApplyDto } from './dto/reset-password-apply.dto'; +import { PasswordResetDto } from './dto/reset-password.dto'; +import { UsersService } from './users.service'; + +@Controller('users') +export class UsersController { + constructor(private usersService: UsersService) {} + + @UseGuards(JwtAuthGuard) + @ApiBearerAuth('JWT') + @ApiOperation({ description: 'Get user profile' }) + @ApiResponse({ status: 200, description: 'Return user profil' }) + @ApiResponse({ status: 401, description: 'User does not have sufficient rights' }) + @Get('profile') + public getProfile(@Request() req) { + return req.user; + } + + @Post() + @ApiResponse({ status: 201, description: 'User created' }) + public async create(@Body() createUserDto: CreateUserDto) { + // remove structureId for creation and add structure after + let structureId = null; + if (createUserDto.pendingStructuresLink.length > 0) { + structureId = createUserDto.pendingStructuresLink[0]; + delete createUserDto.pendingStructuresLink; + } + const user = await this.usersService.create(createUserDto); + if (structureId) { + this.usersService.updateStructureLinked(createUserDto.email, structureId); + } + return user; + } + + @Post('verify/:id') + @ApiParam({ name: 'id', type: String, required: true }) + @ApiResponse({ status: 201, description: 'User verified' }) + @ApiResponse({ status: 401, description: "This token does'nt exist or is not associate to this user." }) + public async validateUser(@Param() params, @Query('token') token: string) { + return this.usersService.validateUser(params.id, token); + } + + @UseGuards(JwtAuthGuard) + @Post('change-password') + @ApiResponse({ status: 201, description: 'Password changed' }) + @ApiResponse({ status: 401, description: 'Invalid password' }) + @ApiResponse({ status: 422, description: 'Weak password' }) + public async changePassword(@Request() req, @Body() passwordChangeDto: PasswordChangeDto) { + return this.usersService.changeUserPassword( + req.user._id, + passwordChangeDto.oldPassword, + passwordChangeDto.newPassword + ); + } + + @UseGuards(JwtAuthGuard) + @Post('change-email') + @ApiResponse({ status: 201, description: 'Email confirmation send' }) + @ApiResponse({ status: 401, description: 'Invalid Email' }) + public async changeEmail(@Request() req, @Body() emailChangeDto: EmailChangeDto) { + return this.usersService.changeUserEmail(emailChangeDto); + } + + @UseGuards(JwtAuthGuard) + @Post('verify-change-email') + @ApiResponse({ status: 201, description: 'Email changed' }) + @ApiResponse({ status: 401, description: 'Invalid Token' }) + public async verifyAndUpdateEmail(@Request() req, @Query('token') token: string) { + return this.usersService.verifyAndUpdateUserEmail(token); + } + + @Post('reset-password') + @ApiResponse({ status: 200, description: 'Email sent if account exist' }) + public async resetPassword(@Body() passwordReset: PasswordResetDto) { + return this.usersService.sendResetPasswordEmail(passwordReset.email); + } + + @Post('reset-password/apply') + @ApiResponse({ status: 200, description: 'Email sent if account exist' }) + public async resetPasswordApply(@Body() passwordResetApplyDto: PasswordResetApplyDto) { + return this.usersService.validatePasswordResetToken(passwordResetApplyDto.password, passwordResetApplyDto.token); + } +} diff --git a/src/users/users.module.ts b/src/users/users.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..ae73bfe01263691656b98892941287ea85642e23 --- /dev/null +++ b/src/users/users.module.ts @@ -0,0 +1,14 @@ +import { Module } from '@nestjs/common'; +import { MongooseModule } from '@nestjs/mongoose'; +import { UsersService } from './users.service'; +import { UsersController } from './users.controller'; +import { User, UserSchema } from './schemas/user.schema'; +import { MailerModule } from '../mailer/mailer.module'; + +@Module({ + imports: [MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]), MailerModule], + providers: [UsersService], + exports: [UsersService], + controllers: [UsersController], +}) +export class UsersModule {} diff --git a/src/users/users.service.spec.ts b/src/users/users.service.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..e24a4807c60a4d93623c6d6b03220032e34905b1 --- /dev/null +++ b/src/users/users.service.spec.ts @@ -0,0 +1,248 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { MailerModule } from '../mailer/mailer.module'; +import { User } from './schemas/user.schema'; +import { UsersService } from './users.service'; +import { getModelToken } from '@nestjs/mongoose'; +import { CreateUserDto } from './dto/create-user.dto'; +import { HttpException, HttpStatus } from '@nestjs/common'; +import { LoginDto } from '../auth/login-dto'; +import { EmailChangeDto } from './dto/change-email.dto'; + +describe('UsersService', () => { + let service: UsersService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [MailerModule], + providers: [ + UsersService, + { + provide: getModelToken('User'), + useValue: User, + }, + ], + }).compile(); + + service = module.get<UsersService>(UsersService); + }); + + describe('User Service create', () => { + it('UsersService should be defined', () => { + expect(service).toBeDefined(); + }); + + it('User should be created', async () => { + const result: User = { + role: 0, + validationToken: + 'cf1c74c22cedb6b575945098db42d2f493fb759c9142c6aff7980f252886f36ee086574ee99a06bc99119079257116c959c8ec870949cebdef2b293666dbca42', + emailVerified: false, + email: 'jacques.dupont@mii.com', + password: '$2a$12$vLQjJ9zAWyUwiXLeQDa6w.yazDArYIpf2WnQF1jRHOjBxADEjUEA3', + newEmail: '', + changeEmailToken: '', + resetPasswordToken: null, + structuresLink: [], + }; + const userDto: CreateUserDto = { email: 'jacques.dupont@mii.com', password: 'test1A!!' }; //NOSONAR + jest.spyOn(service, 'create').mockImplementation(async (): Promise<User> => result); + expect(await service.create(userDto)).toBe(result); + }); + + it('User should not be created, already exist', async () => { + const result = new HttpException('User already exists', HttpStatus.BAD_REQUEST); + const userDto: CreateUserDto = { email: 'jacques.dupont@mii.com', password: 'test1A!!' }; //NOSONAR + jest.spyOn(service, 'create').mockImplementation(async (): Promise<any> => result); + expect(await service.create(userDto)).toBe(result); + }); + + it('User should not be created, weak password', async () => { + const result = new HttpException( + 'Weak password, it must contain ne lowercase alphabetical character, one uppercase alphabetical character, one numeric character, one special character and be eight characters or longer', + HttpStatus.UNPROCESSABLE_ENTITY + ); + const userDto: CreateUserDto = { email: 'jacques.dupont@mii.com', password: 'test' }; //NOSONAR + jest.spyOn(service, 'create').mockImplementation(async (): Promise<any> => result); + expect(await service.create(userDto)).toBe(result); + }); + }); + + describe('findByLogin', () => { + it('should find', async () => { + const result = { + validationToken: + 'cf1c74c22cedb6b575945098db42d2f493fb759c9142c6aff7980f252886f36ee086574ee99a06bc99119079257116c959c8ec870949cebdef2b293666dbca42', + emailVerified: false, + email: 'jacques.dupont@mii.com', + password: '$2a$12$vLQjJ9zAWyUwiXLeQDa6w.yazDArYIpf2WnQF1jRHOjBxADEjUEA3', + role: 0, + newEmail: '', + changeEmailToken: '', + resetPasswordToken: null, + structuresLink: [], + }; + const loginDto: LoginDto = { email: 'jacques.dupont@mii.com', password: 'test1A!!' }; //NOSONAR + jest.spyOn(service, 'findByLogin').mockImplementation(async (): Promise<User> => result); + expect(await service.findByLogin(loginDto)).toBe(result); + }); + + it('user does not exist, should be unauthorized issue', async () => { + const result: HttpException = new HttpException('Invalid credentials', HttpStatus.UNAUTHORIZED); + const loginDto: LoginDto = { email: 'jean.dupont@mii.com', password: 'test1A!!' }; //NOSONAR + jest.spyOn(service, 'findByLogin').mockImplementation(async (): Promise<any> => result); + expect(await service.findByLogin(loginDto)).toBe(result); + }); + + it('wrong password, should be unauthorized issue', async () => { + const result: HttpException = new HttpException('Invalid credentials', HttpStatus.UNAUTHORIZED); + const loginDto: LoginDto = { email: 'jacques.dupont@mii.com', password: 'test1A!!!' }; //NOSONAR + jest.spyOn(service, 'findByLogin').mockImplementation(async (): Promise<any> => result); + expect(await service.findByLogin(loginDto)).toBe(result); + }); + }); + + describe('validateUser', () => { + it('should not validateUser', async () => { + const result = new HttpException('Invalid token', HttpStatus.UNAUTHORIZED); + jest.spyOn(service, 'validateUser').mockImplementation(async (): Promise<HttpException> => result); + expect(await service.validateUser('add3d', 'qdqdqdqd185')).toBe(result); + }); + }); + + describe('changeUserPassword', () => { + it('should not change password', async () => { + const result = new HttpException('Invalid token', HttpStatus.UNAUTHORIZED); + jest.spyOn(service, 'changeUserPassword').mockImplementation(async (): Promise<HttpException> => result); + expect(await service.changeUserPassword('add3d', 'azertyU1', 'azertyU1!d')).toBe(result); + }); + it('should not change password', async () => { + const result = new HttpException('Invalid token', HttpStatus.UNPROCESSABLE_ENTITY); + jest.spyOn(service, 'changeUserPassword').mockImplementation(async (): Promise<HttpException> => result); + expect(await service.changeUserPassword('add3d', 'azertyU1!d', 'a')).toBe(result); + }); + it('should change password', async () => { + const result = new HttpException('Invalid token', HttpStatus.CREATED); + jest.spyOn(service, 'changeUserPassword').mockImplementation(async (): Promise<HttpException> => result); + expect(await service.changeUserPassword('add3d', 'azertyU1!d', 'azertyU1!d')).toBe(result); + }); + }); + + describe('changeUserEmail', () => { + it('should find and add token', async () => { + const result = { + validationToken: '', + emailVerified: true, + email: 'jacques.dupont@mii.com', + password: '$2a$12$vLQjJ9zAWyUwiXLeQDa6w.yazDArYIpf2WnQF1jRHOjBxADEjUEA3', + role: 0, + newEmail: 'test.dupont@mail.com', + resetPasswordToken: '', + structuresLink: [], + changeEmailToken: + '9bb3542bdc5ca8801ad4cee00403c1052bc95dee768dcbb65b1f719870578ed79f71f52fdc3e6bf02fd200a72b8b6f56fc26950df30c8cd7e427a485f80181b9', + }; + const emailDto: EmailChangeDto = { newEmail: 'test.dupont@mail.com', oldEmail: 'jacques.dupont@mii.com' }; //NOSONAR + jest.spyOn(service, 'changeUserEmail').mockImplementation(async (): Promise<User> => result); + expect(await service.changeUserEmail(emailDto)).toBe(result); + }); + it('user does not exist, should be unauthorized issue', async () => { + const result: HttpException = new HttpException('Email sent if account exist', HttpStatus.UNAUTHORIZED); + const emailDto: EmailChangeDto = { newEmail: 'test.dupont@mail.com', oldEmail: 'jacques.dupont@mii.com' }; //NOSONAR + jest.spyOn(service, 'changeUserEmail').mockImplementation(async (): Promise<any> => result); + expect(await service.changeUserEmail(emailDto)).toBe(result); + }); + it('email already used, should be not acceptable issue', async () => { + const result: HttpException = new HttpException('Email already used', HttpStatus.NOT_ACCEPTABLE); + const emailDto: EmailChangeDto = { newEmail: 'jacques.dupont@mii.com', oldEmail: 'jacques.dupont@mii.com' }; //NOSONAR + jest.spyOn(service, 'changeUserEmail').mockImplementation(async (): Promise<any> => result); + expect(await service.changeUserEmail(emailDto)).toBe(result); + }); + it('should change email', async () => { + const result = { + validationToken: '', + emailVerified: true, + email: 'test.dupont@mail.com', + password: '$2a$12$vLQjJ9zAWyUwiXLeQDa6w.yazDArYIpf2WnQF1jRHOjBxADEjUEA3', + role: 0, + newEmail: '', + resetPasswordToken: '', + changeEmailToken: '', + structuresLink: [], + }; + const token = + '9bb3542bdc5ca8801ad4cee00403c1052bc95dee768dcbb65b1f719870578ed79f71f52fdc3e6bf02fd200a72b8b6f56fc26950df30c8cd7e427a485f80181b9'; //NOSONAR + jest.spyOn(service, 'verifyAndUpdateUserEmail').mockImplementation(async (): Promise<User> => result); + expect(await service.verifyAndUpdateUserEmail(token)).toBe(result); + }); + it('should not change email', async () => { + const result: HttpException = new HttpException('Invalid token', HttpStatus.UNAUTHORIZED); + const token = '9bb3542bdc5ca8801aa72b8b6f56fc26950df30c8cd7e427a485f80181b9FAKETOKEN'; //NOSONAR + jest.spyOn(service, 'verifyAndUpdateUserEmail').mockImplementation(async (): Promise<any> => result); + expect(await service.verifyAndUpdateUserEmail(token)).toBe(result); + }); + }); + + describe('sendResetPasswordEmail', () => { + it('should not send email', async () => { + const result = new HttpException('Email sent if account exist', HttpStatus.OK); + jest.spyOn(service, 'sendResetPasswordEmail').mockImplementation(async (): Promise<HttpException> => result); + expect(await service.sendResetPasswordEmail('test@mii.com')).toBe(result); + }); + + it('should send email', async () => { + const result = new HttpException('Email sent if account exist', HttpStatus.OK); + jest.spyOn(service, 'sendResetPasswordEmail').mockImplementation(async (): Promise<HttpException> => result); + expect(await service.sendResetPasswordEmail('test@mii.com')).toBe(result); + }); + }); + + describe('validatePasswordResetToken', () => { + it('should not validate new password: token does`nt exist', async () => { + const result = new HttpException('Invalid token', HttpStatus.UNAUTHORIZED); + jest.spyOn(service, 'validatePasswordResetToken').mockImplementation(async (): Promise<HttpException> => result); + expect( + await service.validatePasswordResetToken( + 'test@mii.com', + '5def4cb41106f89c212679e164911776618bd529e4f78e2883f7dd01776612a1b4a2ad7edabf2a3e3638aa605966c7a4b69d5f07d9617334e58332ba5f9305' + ) + ).toBe(result); + }); + + it('should not validate new password: weak password', async () => { + const result = new HttpException( + 'Weak password, it must contain ne lowercase alphabetical character, one uppercase alphabetical character, one numeric character, one special character and be eight characters or longer', + HttpStatus.UNPROCESSABLE_ENTITY + ); + jest.spyOn(service, 'validatePasswordResetToken').mockImplementation(async (): Promise<HttpException> => result); + expect( + await service.validatePasswordResetToken( + 'test@mii.com', + '5def4cb41106f89c212679e164911776618bd529e4f78e2883f7dd01776612a1b4a2ad7edabf2a3e3638aa605966c7a4b69d5f07d9617334e58332ba5f9305a6' + ) + ).toBe(result); + }); + + it('should validate new password', async () => { + const result = new HttpException('Password Reset', HttpStatus.OK); + jest.spyOn(service, 'validatePasswordResetToken').mockImplementation(async (): Promise<HttpException> => result); + expect( + await service.validatePasswordResetToken( + 'test@mii.com', + '5def4cb41106f89c212679e164911776618bd529e4f78e2883f7dd01776612a1b4a2ad7edabf2a3e3638aa605966c7a4b69d5f07d9617334e58332ba5f9305a6' + ) + ).toBe(result); + }); + + it('should return structureLink tab ', async () => { + const result = [53]; + jest.spyOn(service, 'updateStructureLinked').mockImplementation(async (): Promise<any> => result); + expect(await service.updateStructureLinked('test@mii.com', 53)).toBe(result); + }); + + it('should return invalid User ', async () => { + const result = new HttpException('Invalid user', HttpStatus.NOT_FOUND); + jest.spyOn(service, 'updateStructureLinked').mockImplementation(async (): Promise<any> => result); + expect(await service.updateStructureLinked('test@mii.com', 53)).toBe(result); + }); + }); +}); diff --git a/src/users/users.service.ts b/src/users/users.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..f683ebdca2a3a881a199c5b85964daf50557db6f --- /dev/null +++ b/src/users/users.service.ts @@ -0,0 +1,410 @@ +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { InjectModel } from '@nestjs/mongoose'; +import * as bcrypt from 'bcrypt'; +import * as ejs from 'ejs'; +import * as crypto from 'crypto'; +import { Model, Types } from 'mongoose'; +import { LoginDto } from '../auth/login-dto'; +import { CreateUserDto } from './dto/create-user.dto'; +import { User } from './schemas/user.schema'; +import { MailerService } from '../mailer/mailer.service'; +import { IUser } from './interfaces/user.interface'; +import { EmailChangeDto } from './dto/change-email.dto'; +import { PendingStructureDto } from '../admin/dto/pending-structure.dto'; + +@Injectable() +export class UsersService { + constructor(@InjectModel(User.name) private userModel: Model<IUser>, private readonly mailerService: MailerService) {} + + /** + * Create a user account + * @param createUserDto CreateUserDto + */ + public async create(createUserDto: CreateUserDto): Promise<User> { + const userInDb = await this.findOne(createUserDto.email); + if (userInDb) { + throw new HttpException('User already exists', HttpStatus.BAD_REQUEST); + } + if (!this.isStrongPassword(createUserDto.password)) { + throw new HttpException( + 'Weak password, it must contain ne lowercase alphabetical character, one uppercase alphabetical character, one numeric character, one special character and be eight characters or longer', + HttpStatus.UNPROCESSABLE_ENTITY + ); + } + let createUser = new this.userModel(createUserDto); + // createUser.email = createUserDto.email; + createUser.password = await this.hashPassword(createUser.password); + // Send verification email + createUser = await this.verifyUserMail(createUser); + createUser.save(); + return await this.findOne(createUserDto.email); + } + + /** + * Verify password strenth with the following rule: + * - The string must contain at least 1 lowercase alphabetical character + * - The string must contain at least 1 uppercase alphabetical character + * - The string must contain at least 1 numeric character + * - The string must contain at least one special character, reserved RegEx characters are escaped to avoid conflict + * - The string must be eight characters or longer + * @param password string + */ + private isStrongPassword(password: string): boolean { + const strongRegex = new RegExp('^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{8,})'); //NOSONAR + return strongRegex.test(password); + } + + private async comparePassword(attempt: string, password: string): Promise<boolean> { + return await bcrypt.compare(attempt, password); + } + + private async hashPassword(password: string): Promise<string> { + return await bcrypt.hash(password, process.env.SALT); + } + + public async findOne(mail: string, passwordQuery?: boolean): Promise<IUser | undefined> { + if (passwordQuery) { + return this.userModel.findOne({ email: mail }).exec(); + } + return this.userModel.findOne({ email: mail }).select('-password').exec(); + } + + public async findAll(): Promise<User[]> { + return await this.userModel.find().exec(); + } + + public async findById(id: string, passwordQuery?: boolean): Promise<IUser | undefined> { + if (passwordQuery) { + return this.userModel.findById(id).exec(); + } + return this.userModel.findById(id).select('-password').exec(); + } + + /** + * Return a user after credential checking. + * Use for login action + * @param param LoginDto + */ + public async findByLogin({ email, password }: LoginDto): Promise<User> { + const user = await this.findOne(email, true); + + if (!user) { + throw new HttpException('Invalid credentials', HttpStatus.UNAUTHORIZED); + } + + // compare passwords + const areEqual = await this.comparePassword(password, user.password); + + if (!areEqual) { + throw new HttpException('Invalid credentials', HttpStatus.UNAUTHORIZED); + } + + return user; + } + + /** + * Generate activation token and send it to user by email, in order to validate + * a new account. + * @param user User + */ + private async verifyUserMail(user: IUser): Promise<any> { + const config = this.mailerService.config; + const ejsPath = this.mailerService.getTemplateLocation(config.templates.verify.ejs); + const jsonConfig = this.mailerService.loadJsonConfig(config.templates.verify.json); + + const token = crypto.randomBytes(64).toString('hex'); + const html = await ejs.renderFile(ejsPath, { + config, + token: token, + userId: user._id, + }); + this.mailerService.send(user.email, jsonConfig.subject, html); + + // Save token + user.validationToken = token; + return user; + } + + /** + * Send to all admins validation email for structures + * a new account. + */ + private async sendAdminStructureValidationMail(): Promise<any> { + const config = this.mailerService.config; + const ejsPath = this.mailerService.getTemplateLocation(config.templates.adminStructureClaim.ejs); + const jsonConfig = this.mailerService.loadJsonConfig(config.templates.adminStructureClaim.json); + + const html = await ejs.renderFile(ejsPath, { + config, + }); + const admins = await this.getAdmins(); + admins.forEach((admin) => { + this.mailerService.send(admin.email, jsonConfig.subject, html); + }); + } + + /** + * Send to all admins mail for aptic duplicated data + */ + public async sendAdminApticStructureMail(structureName: string, duplicatedStructureName: string): Promise<any> { + const config = this.mailerService.config; + const ejsPath = this.mailerService.getTemplateLocation(config.templates.apticStructureDuplication.ejs); + const jsonConfig = this.mailerService.loadJsonConfig(config.templates.apticStructureDuplication.json); + + const html = await ejs.renderFile(ejsPath, { + config, + name: structureName, + duplicatedStructureName: duplicatedStructureName, + }); + const admins = await this.getAdmins(); + admins.forEach((admin) => { + this.mailerService.send(admin.email, jsonConfig.subject, html); + }); + } + + /** + * Send approval email for user + * a new account. + * @param user User + */ + private async sendStructureClaimApproval(userEmail: string, structureName: string, status: boolean): Promise<any> { + const config = this.mailerService.config; + const ejsPath = this.mailerService.getTemplateLocation(config.templates.structureClaimValidation.ejs); + const jsonConfig = this.mailerService.loadJsonConfig(config.templates.structureClaimValidation.json); + + const html = await ejs.renderFile(ejsPath, { + config, + status: status ? 'accepté' : 'refusée', + name: structureName, + }); + this.mailerService.send(userEmail, jsonConfig.subject, html); + } + + /** + * Check that the given token is associated to userId. If it's true, validate user account. + * @param userId string + * @param token string + */ + public async validateUser(userId: string, token: string): Promise<User> { + const user = await this.findById(userId); + if (user && user.validationToken === token) { + user.validationToken = null; + user.emailVerified = true; + user.save(); + return user; + } else { + throw new HttpException('Invalid token', HttpStatus.UNAUTHORIZED); + } + } + + public async changeUserEmail(emailDto: EmailChangeDto): Promise<any> { + const user = await this.findOne(emailDto.oldEmail); + const alreadyUsed = await this.findOne(emailDto.newEmail); + if (user) { + if (!alreadyUsed) { + const config = this.mailerService.config; + const ejsPath = this.mailerService.getTemplateLocation(config.templates.changeEmail.ejs); + const jsonConfig = this.mailerService.loadJsonConfig(config.templates.changeEmail.json); + const token = crypto.randomBytes(64).toString('hex'); + const html = await ejs.renderFile(ejsPath, { + config, + token: token, + }); + this.mailerService.send(user.email, jsonConfig.subject, html); + user.changeEmailToken = token; + user.newEmail = emailDto.newEmail; + user.save(); + return user; + } + throw new HttpException('Email already used', HttpStatus.NOT_ACCEPTABLE); + } + throw new HttpException('Email sent if account exist', HttpStatus.UNAUTHORIZED); + } + + public async verifyAndUpdateUserEmail(token: string): Promise<any> { + const user = await this.userModel.findOne({ changeEmailToken: token }).exec(); + if (user) { + user.email = user.newEmail; + user.newEmail = null; + user.changeEmailToken = null; + user.save(); + return user; + } else { + throw new HttpException('Invalid token', HttpStatus.UNAUTHORIZED); + } + } + + public async changeUserPassword(userId: string, oldPassword: string, newPassword: string): Promise<any> { + const user = await this.findById(userId, true); + const arePasswordEqual = await this.comparePassword(oldPassword, user.password); + if (!arePasswordEqual) { + throw new HttpException('Invalid credentials', HttpStatus.UNAUTHORIZED); + } + if (!this.isStrongPassword(newPassword)) { + throw new HttpException( + 'Weak password, it must contain ne lowercase alphabetical character, one uppercase alphabetical character, one numeric character, one special character and be eight characters or longer', + HttpStatus.UNPROCESSABLE_ENTITY + ); + } + user.password = await this.hashPassword(newPassword); + user.save(); + } + + /** + * Send reset password email based on ejs template + * @param email string + */ + public async sendResetPasswordEmail(email: string): Promise<HttpException> { + const user = await this.findOne(email); + if (user) { + const config = this.mailerService.config; + const ejsPath = this.mailerService.getTemplateLocation(config.templates.resetPassword.ejs); + const jsonConfig = this.mailerService.loadJsonConfig(config.templates.resetPassword.json); + + const token = crypto.randomBytes(64).toString('hex'); + const html = await ejs.renderFile(ejsPath, { + config, + token: token, + }); + this.mailerService.send(user.email, jsonConfig.subject, html); + + // Save token + user.resetPasswordToken = token; + user.save(); + } + throw new HttpException('Email sent if account exist', HttpStatus.OK); + } + + /** + * Change password with the given token and password + * Token existence and password strength are verified + * @param password string + * @param token string + */ + public async validatePasswordResetToken(password: string, token: string): Promise<HttpException> { + const user = await this.userModel.findOne({ resetPasswordToken: token }).exec(); + if (user) { + if (!this.isStrongPassword(password)) { + throw new HttpException( + 'Weak password, it must contain ne lowercase alphabetical character, one uppercase alphabetical character, one numeric character, one special character and be eight characters or longer', + HttpStatus.UNPROCESSABLE_ENTITY + ); + } + user.password = await this.hashPassword(password); + user.resetPasswordToken = null; + user.save(); + throw new HttpException('Password Reset', HttpStatus.OK); + } + throw new HttpException('Invalid token', HttpStatus.UNAUTHORIZED); + } + + public async getAdmins(): Promise<User[]> { + return this.userModel.find({ role: 1 }).exec(); + } + + public async isStructureClaimed(structureId: string): Promise<IUser> { + return this.userModel.findOne({ structuresLink: Types.ObjectId(structureId) }).exec(); + } + + public async isUserAlreadyClaimedStructure(structureId: string, userEmail: string): Promise<boolean> { + const user = await this.findOne(userEmail, true); + if (user) { + return user.pendingStructuresLink.includes(Types.ObjectId(structureId)); + } + return false; + } + + public async updateStructureLinked(userEmail: string, idStructure: string): Promise<Types.ObjectId[]> { + const user = await this.findOne(userEmail, true); + if (user) { + if (!user.pendingStructuresLink.includes(Types.ObjectId(idStructure))) { + user.pendingStructuresLink.push(Types.ObjectId(idStructure)); + user.save(); + this.sendAdminStructureValidationMail(); + return user.pendingStructuresLink; + } + throw new HttpException('User already claimed this structure', HttpStatus.NOT_FOUND); + } + throw new HttpException('Invalid user', HttpStatus.NOT_FOUND); + } + + /** + * Return all pending attachments of all profiles + */ + public async getPendingStructures(): Promise<PendingStructureDto[]> { + const users = await this.userModel.find(); + const structuresPending = []; + + // For each user, if they have structures in pending, push them in tab and return this tab. + users.forEach((user) => { + if (user.pendingStructuresLink.length) { + user.pendingStructuresLink.forEach((structureId) => { + structuresPending.push({ userEmail: user.email, structureId: structureId }); + }); + } + }); + return structuresPending; + } + + /** + * Validate or refuse a pending structure given a email and structure id + */ + public async validatePendingStructure( + userEmail: string, + structureId: string, + structureName: string, + validate: boolean + ): Promise<PendingStructureDto[]> { + const user = await this.findOne(userEmail); + // Get other users who have made the demand on the same structure + const otherUsers = await this.userModel + .find({ pendingStructuresLink: Types.ObjectId(structureId), email: { $ne: userEmail } }) + .exec(); + + let status = false; + if (!user) { + throw new HttpException('User not found', HttpStatus.NOT_FOUND); + } + if (user.pendingStructuresLink.includes(Types.ObjectId(structureId))) { + user.pendingStructuresLink = user.pendingStructuresLink.filter((item) => { + return !Types.ObjectId(structureId).equals(item); + }); + // If it's a validation case, push structureId into validated user structures + if (validate) { + user.structuresLink.push(Types.ObjectId(structureId)); + // Send validation email + status = true; + // For other users who have made the demand on the same structure + if (otherUsers) { + otherUsers.forEach((user) => { + // Remove the structure id from their demand + user.pendingStructuresLink = user.pendingStructuresLink.filter((item) => { + return !Types.ObjectId(structureId).equals(item); + }); + // Send a rejection email + this.sendStructureClaimApproval(user.email, structureName, false); + user.save(); + }); + } + } + this.sendStructureClaimApproval(userEmail, structureName, status); + await user.save(); + return this.getPendingStructures(); + } else { + throw new HttpException( + 'Cannot validate strucutre. It might have been already validate, or the structure does`nt belong to the user', + HttpStatus.NOT_FOUND + ); + } + } + + public async removeOutdatedStructureFromArray(structureId: string): Promise<void> { + const users = await this.userModel.find({ structureOutdatedMailSent: Types.ObjectId(structureId) }).exec(); + users.forEach((user) => { + user.structureOutdatedMailSent = user.structureOutdatedMailSent.filter((item) => + Types.ObjectId(structureId).equals(item) + ); + user.save(); + }); + } +} diff --git a/template.env b/template.env index bd0a81b58efd1d682e148deff4171376ae8dc3a6..fa2c9d0ae8b15dad61e8e27bf04059602c8c597c 100644 --- a/template.env +++ b/template.env @@ -1,6 +1,7 @@ TAG=<version number> +NODE_ENV=<dev or production> SERVICE_API_BIND_PORT=<service port> -ACCESS_TOKEN_COOKIE_KEY=<cookie key where the access token will be stored> +JWT_SECRET=<the secret used to sign jwt token> MONGO_ROOT_PASSWORD=<mongo root user password> MONGO_NON_ROOT_USERNAME=<mongo non root username> MONGO_NON_ROOT_PASSWORD=<mongo non root user password> @@ -9,5 +10,9 @@ MONGO_DB_HOST_AND_PORT=<host:port used by the api to connect to mongo db> ME_CONFIG_BASICAUTH_USERNAME=<mongo express username> ME_CONFIG_BASICAUTH_PASSWORD=<mongo express password> ME_PORT=<mongo express port> +SALT=<Salt must be in the form of: $Vers$log2(NumRounds)$saltvalue> MAIL_URL=<API url> -MAIL_TOKEN=<API token> \ No newline at end of file +MAIL_TOKEN=<API token> +APTIC_TOKEN=<APTIC API TOKEN> +GHOST_PORT=<ghost port> +GHOST_DB_PASSWORD=<ghost db password>