diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..fadb264 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,27 @@ +module.exports = { + env: { + browser: true, + commonjs: true, + es2021: true, + }, + extends: 'standard', + plugins: ['prettier'], + overrides: [ + { + env: { + node: true, + }, + files: ['.eslintrc.{js,cjs}'], + parserOptions: { + sourceType: 'script', + }, + }, + ], + parserOptions: { + ecmaVersion: 'latest', + }, + rules: { + 'comma-dangle': ['error', 'never'], + 'no-extra-semi': ['error', 'never'], + }, +}; diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..6442ae0 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "tabWidth": 2, + "singleQuote": true, + "semi": true, + "trailingComma": "all", + "endOfLine": "lf" +} diff --git a/README.md b/README.md index aca48ce..5efc7fa 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,40 @@ ## Important Information + This repository is exclusively meant for presenting samples of code. The Files and Codes present in this repository are part of a running project. In order to ensure the security of the project several files have been intentionally removed. The codes are exclusively for viewing purpose and will not execute properly if tried. # Express ES2017 REST API Boilerplate - Boilerplate/Generator/Starter Project for building RESTful APIs and microservices using Node.js, Express and MongoDB ## Features - - No transpilers, just vanilla javascript - - ES2017 latest features like Async/Await - - CORS enabled - - Uses [yarn](https://yarnpkg.com) - - Express + MongoDB ([Mongoose](http://mongoosejs.com/)) - - Consistent coding styles with [editorconfig](http://editorconfig.org) - - [Docker](https://www.docker.com/) support - - Uses [helmet](https://github.com/helmetjs/helmet) to set some HTTP headers for security - - Load environment variables from .env files with [dotenv](https://github.com/rolodato/dotenv-safe) - - Request validation with [joi](https://github.com/hapijs/joi) - - Gzip compression with [compression](https://github.com/expressjs/compression) - - Linting with [eslint](http://eslint.org) - - Tests with [mocha](https://mochajs.org), [chai](http://chaijs.com) and [sinon](http://sinonjs.org) - - Code coverage with [istanbul](https://istanbul.js.org) and [coveralls](https://coveralls.io) - - Git hooks with [husky](https://github.com/typicode/husky) - - Logging with [morgan](https://github.com/expressjs/morgan) - - Authentication and Authorization with [passport](http://passportjs.org) - - API documentation generation with [apidoc](http://apidocjs.com) - - Continuous integration support with [travisCI](https://travis-ci.org) - - Monitoring with [pm2](https://github.com/Unitech/pm2) +- No transpilers, just vanilla javascript +- ES2017 latest features like Async/Await +- CORS enabled +- Uses [yarn](https://yarnpkg.com) +- Express + MongoDB ([Mongoose](http://mongoosejs.com/)) +- Consistent coding styles with [editorconfig](http://editorconfig.org) +- [Docker](https://www.docker.com/) support +- Uses [helmet](https://github.com/helmetjs/helmet) to set some HTTP headers for security +- Load environment variables from .env files with [dotenv](https://github.com/rolodato/dotenv-safe) +- Request validation with [joi](https://github.com/hapijs/joi) +- Gzip compression with [compression](https://github.com/expressjs/compression) +- Linting with [eslint](http://eslint.org) +- Tests with [mocha](https://mochajs.org), [chai](http://chaijs.com) and [sinon](http://sinonjs.org) +- Code coverage with [istanbul](https://istanbul.js.org) and [coveralls](https://coveralls.io) +- Git hooks with [husky](https://github.com/typicode/husky) +- Logging with [morgan](https://github.com/expressjs/morgan) +- Authentication and Authorization with [passport](http://passportjs.org) +- API documentation generation with [apidoc](http://apidocjs.com) +- Continuous integration support with [travisCI](https://travis-ci.org) +- Monitoring with [pm2](https://github.com/Unitech/pm2) ## Requirements - - [Node v7.6+](https://nodejs.org/en/download/current/) or [Docker](https://www.docker.com/) - - [Yarn](https://yarnpkg.com/en/docs/install) +- [Node v7.6+](https://nodejs.org/en/download/current/) or [Docker](https://www.docker.com/) +- [Yarn](https://yarnpkg.com/en/docs/install) ## Getting Started @@ -155,4 +155,3 @@ Run deploy script: ```bash yarn deploy ``` - diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 8c637dc..1ff013c 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -1,4 +1,4 @@ -version: "2" +version: '2' services: boilerplate-api: command: yarn dev -- -L diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 2134f5b..efbcc4b 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -1,4 +1,4 @@ -version: "2" +version: '2' services: boilerplate-api: command: yarn start diff --git a/docker-compose.test.yml b/docker-compose.test.yml index c742346..0bf9e34 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -1,4 +1,4 @@ -version: "2" +version: '2' services: boilerplate-api: command: yarn test diff --git a/docker-compose.yml b/docker-compose.yml index eb91a80..c1927c0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,4 @@ -version: "2" +version: '2' services: boilerplate-api: build: . @@ -7,11 +7,11 @@ services: volumes: - .:/app ports: - - "3000:3000" + - '3000:3000' depends_on: - mongodb mongodb: image: mongo ports: - - "27017:27017" + - '27017:27017' diff --git a/package-lock.json b/package-lock.json index 57d2c04..b4455d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,8 +14,8 @@ "body-parser": "^1.19.1", "compression": "^1.7.4", "cors": "^2.8.5", - "cross-env": "^7.0.3", "dotenv": "^16.4.5", + "dotenv-cli": "^7.4.1", "email-templates": "^11.1.1", "express": "^4.17.2", "express-validation": "^4.1.0", @@ -24,6 +24,7 @@ "joi": "^17.5.0", "jwt-simple": "^0.5.6", "method-override": "^3.0.0", + "moment-timezone": "^0.5.45", "mongoose": "^8.2.1", "morgan": "^1.10.0", "nodemailer": "^6.7.2", @@ -40,14 +41,18 @@ "chai": "^4.4.1", "chai-as-promised": "^7.1.1", "coveralls": "^3.1.1", - "eslint": "^8.5.0", - "eslint-config-airbnb-base": "^15.0.0", - "eslint-plugin-import": "^2.25.3", + "eslint": "^8.57.0", + "eslint-config-standard": "^17.1.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-n": "^16.6.2", + "eslint-plugin-prettier": "^5.1.3", + "eslint-plugin-promise": "^6.1.1", "husky": "^9.0.11", "mocha": "^10.3.0", "nodemon": "^3.1.0", "nyc": "^15.1.0", "opn-cli": "^5.0.0", + "prettier": "^3.2.5", "sinon": "^17.0.1", "sinon-chai": "^3.7.0", "supertest": "^6.1.6" @@ -1341,6 +1346,18 @@ "uuid": "bin/uuid" } }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/@pm2/agent": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@pm2/agent/-/agent-2.0.3.tgz", @@ -2898,6 +2915,27 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, + "dependencies": { + "semver": "^7.0.0" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -3369,12 +3407,6 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, - "node_modules/confusing-browser-globals": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", - "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", - "dev": true - }, "node_modules/constantinople": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", @@ -3498,23 +3530,6 @@ "resolved": "https://registry.npmjs.org/croner/-/croner-4.1.97.tgz", "integrity": "sha512-/f6gpQuxDaqXu+1kwQYSckUglPaOrHdbIlBAu0YuW8/Cdb45XwXYNUBXg3r/9Mo6n540Kn/smKcZWko5x99KrQ==" }, - "node_modules/cross-env": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", - "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", - "dependencies": { - "cross-spawn": "^7.0.1" - }, - "bin": { - "cross-env": "src/bin/cross-env.js", - "cross-env-shell": "src/bin/cross-env-shell.js" - }, - "engines": { - "node": ">=10.14", - "npm": ">=6", - "yarn": ">=1" - } - }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -3891,6 +3906,28 @@ "url": "https://dotenvx.com" } }, + "node_modules/dotenv-cli": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-7.4.1.tgz", + "integrity": "sha512-fE1aywjRrWGxV3miaiUr3d2zC/VAiuzEGghi+QzgIA9fEf/M5hLMaRSXb4IxbUAwGmaLi0IozdZddnVU96acag==", + "dependencies": { + "cross-spawn": "^7.0.3", + "dotenv": "^16.3.0", + "dotenv-expand": "^10.0.0", + "minimist": "^1.2.6" + }, + "bin": { + "dotenv": "cli.js" + } + }, + "node_modules/dotenv-expand": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz", + "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==", + "engines": { + "node": ">=12" + } + }, "node_modules/ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -4352,32 +4389,45 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-config-airbnb-base": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", - "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", + "node_modules/eslint-compat-utils": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.1.2.tgz", + "integrity": "sha512-Jia4JDldWnFNIru1Ehx1H5s9/yxiRHY/TimCuUc0jNexew3cF1gI6CYZil1ociakfWO3rRqFjl1mskBblB3RYg==", "dev": true, - "dependencies": { - "confusing-browser-globals": "^1.0.10", - "object.assign": "^4.1.2", - "object.entries": "^1.1.5", - "semver": "^6.3.0" - }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=12" }, "peerDependencies": { - "eslint": "^7.32.0 || ^8.2.0", - "eslint-plugin-import": "^2.25.2" + "eslint": ">=6.0.0" } }, - "node_modules/eslint-config-airbnb-base/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/eslint-config-standard": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz", + "integrity": "sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==", "dev": true, - "bin": { - "semver": "bin/semver.js" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "eslint": "^8.0.1", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-n": "^15.0.0 || ^16.0.0 ", + "eslint-plugin-promise": "^6.0.0" } }, "node_modules/eslint-import-resolver-node": { @@ -4438,6 +4488,26 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, + "node_modules/eslint-plugin-es-x": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.5.0.tgz", + "integrity": "sha512-ODswlDSO0HJDzXU0XvgZ3lF3lS3XAZEossh15Q2UHjwrJggWeBoKqqEsLTZLXl+dh5eOAozG0zRcYtuE35oTuQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.1.2", + "@eslint-community/regexpp": "^4.6.0", + "eslint-compat-utils": "^0.1.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "eslint": ">=8" + } + }, "node_modules/eslint-plugin-import": { "version": "2.29.1", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", @@ -4505,6 +4575,76 @@ "semver": "bin/semver.js" } }, + "node_modules/eslint-plugin-n": { + "version": "16.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.6.2.tgz", + "integrity": "sha512-6TyDmZ1HXoFQXnhCTUjVFULReoBPOAjpuiKELMkeP40yffI/1ZRO+d9ug/VC6fqISo2WkuIBk3cvuRPALaWlOQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "builtins": "^5.0.1", + "eslint-plugin-es-x": "^7.5.0", + "get-tsconfig": "^4.7.0", + "globals": "^13.24.0", + "ignore": "^5.2.4", + "is-builtin-module": "^3.2.1", + "is-core-module": "^2.12.1", + "minimatch": "^3.1.2", + "resolve": "^1.22.2", + "semver": "^7.5.3" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", + "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.6" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-promise": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", + "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, "node_modules/eslint-scope": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", @@ -4876,6 +5016,12 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, "node_modules/fast-json-patch": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz", @@ -5361,6 +5507,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-tsconfig": { + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.3.tgz", + "integrity": "sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/get-uri": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz", @@ -6117,6 +6275,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "dev": true, + "dependencies": { + "builtin-modules": "^3.3.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -7673,6 +7846,25 @@ "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz", "integrity": "sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==" }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "engines": { + "node": "*" + } + }, + "node_modules/moment-timezone": { + "version": "0.5.45", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.45.tgz", + "integrity": "sha512-HIWmqA86KcmCAhnMAN0wuDOARV/525R2+lOLotuGFzn4HO+FH+/645z2wx0Dt3iDv6/p61SIvKnDstISainhLQ==", + "dependencies": { + "moment": "^2.29.4" + }, + "engines": { + "node": "*" + } + }, "node_modules/mongodb": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.3.0.tgz", @@ -8393,20 +8585,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object.entries": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz", - "integrity": "sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/object.fromentries": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", @@ -9288,6 +9466,33 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/preview-email": { "version": "3.0.19", "resolved": "https://registry.npmjs.org/preview-email/-/preview-email-3.0.19.tgz", @@ -10040,6 +10245,15 @@ "node": ">=4" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -10897,6 +11111,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/synckit": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", + "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", + "dev": true, + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/systeminformation": { "version": "5.22.0", "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.22.0.tgz", diff --git a/package.json b/package.json index 27da77b..c28b0eb 100644 --- a/package.json +++ b/package.json @@ -11,15 +11,16 @@ "scripts": { "precommit": "yarn lint", "prestart": "yarn docs", - "start": "cross-env NODE_ENV=production pm2 start ./src/index.js", - "dev": "nodemon ./src/index.js", + "start:dev": "dotenv -v NODE_ENV=development -- nodemon ./src/index.js", + "start:prod": "dotenv -v NODE_ENV=production -- pm2 start ./src/index.js", "lint": "eslint ./src/ --ignore-path .gitignore --ignore-pattern internals/scripts", - "lint:fix": "yarn lint --fix", - "lint:watch": "yarn lint --watch", - "test": "cross-env NODE_ENV=test nyc --reporter=html --reporter=text mocha --timeout 20000 --exit --recursive src/api/tests", - "test:unit": "cross-env NODE_ENV=test mocha src/api/tests/unit", - "test:integration": "cross-env NODE_ENV=test mocha --timeout 20000 --exit src/api/tests/integration", - "test:watch": "cross-env NODE_ENV=test mocha --watch src/api/tests/unit", + "lint:fix": "npx eslint --fix", + "lint:watch": "npx eslint --watch", + "format": "npx prettier --write .", + "test": "dotenv -v NODE_ENV=test -- nyc --reporter=html --reporter=text mocha --timeout 20000 --exit --recursive src/api/tests", + "test:unit": "dotenv -v NODE_ENV=test -- mocha src/api/tests/unit", + "test:integration": "dotenv -v NODE_ENV=test -- mocha --timeout 20000 --exit src/api/tests/integration", + "test:watch": "dotenv -v NODE_ENV=test -- mocha --watch src/api/tests/unit", "coverage": "nyc report --reporter=text-lcov | coveralls", "postcoverage": "open-cli coverage/lcov-report/index.html", "validate": "yarn lint && yarn test", @@ -66,8 +67,8 @@ "body-parser": "^1.19.1", "compression": "^1.7.4", "cors": "^2.8.5", - "cross-env": "^7.0.3", "dotenv": "^16.4.5", + "dotenv-cli": "^7.4.1", "email-templates": "^11.1.1", "express": "^4.17.2", "express-validation": "^4.1.0", @@ -76,6 +77,7 @@ "joi": "^17.5.0", "jwt-simple": "^0.5.6", "method-override": "^3.0.0", + "moment-timezone": "^0.5.45", "mongoose": "^8.2.1", "morgan": "^1.10.0", "nodemailer": "^6.7.2", @@ -92,14 +94,18 @@ "chai": "^4.4.1", "chai-as-promised": "^7.1.1", "coveralls": "^3.1.1", - "eslint": "^8.5.0", - "eslint-config-airbnb-base": "^15.0.0", - "eslint-plugin-import": "^2.25.3", + "eslint": "^8.57.0", + "eslint-config-standard": "^17.1.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-n": "^16.6.2", + "eslint-plugin-prettier": "^5.1.3", + "eslint-plugin-promise": "^6.1.1", "husky": "^9.0.11", "mocha": "^10.3.0", "nodemon": "^3.1.0", "nyc": "^15.1.0", "opn-cli": "^5.0.0", + "prettier": "^3.2.5", "sinon": "^17.0.1", "sinon-chai": "^3.7.0", "supertest": "^6.1.6" diff --git a/src/api/controllers/auth.controller.js b/src/api/controllers/auth.controller.js index 377380d..db638e0 100644 --- a/src/api/controllers/auth.controller.js +++ b/src/api/controllers/auth.controller.js @@ -84,7 +84,10 @@ exports.refresh = async (req, res, next) => { userEmail: email, token: refreshToken, }); - const { user, accessToken } = await User.findAndGenerateToken({ email, refreshObject }); + const { user, accessToken } = await User.findAndGenerateToken({ + email, + refreshObject, + }); const response = generateTokenResponse(user, accessToken); return res.json(response); } catch (error) { @@ -133,7 +136,9 @@ exports.resetPassword = async (req, res, next) => { throw new APIError(err); } - const user = await User.findOne({ email: resetTokenObject.userEmail }).exec(); + const user = await User.findOne({ + email: resetTokenObject.userEmail, + }).exec(); user.password = password; await user.save(); emailProvider.sendPasswordChangeEmail(user); diff --git a/src/api/controllers/user.controller.js b/src/api/controllers/user.controller.js index f14c8f2..a04159f 100644 --- a/src/api/controllers/user.controller.js +++ b/src/api/controllers/user.controller.js @@ -54,7 +54,6 @@ exports.replace = async (req, res, next) => { const ommitRole = user.role !== 'admin' ? 'role' : ''; const newUserObject = omit(newUser.toObject(), '_id', ommitRole); - res.json(savedUser.transform()); } catch (error) { next(User.checkDuplicateEmail(error)); @@ -70,9 +69,10 @@ exports.update = (req, res, next) => { const updatedUser = omit(req.body, ommitRole); const user = Object.assign(req.locals.user, updatedUser); - user.save() - .then(savedUser => res.json(savedUser.transform())) - .catch(e => next(User.checkDuplicateEmail(e))); + user + .save() + .then((savedUser) => res.json(savedUser.transform())) + .catch((e) => next(User.checkDuplicateEmail(e))); }; /** @@ -82,7 +82,7 @@ exports.update = (req, res, next) => { exports.list = async (req, res, next) => { try { const users = await User.list(req.query); - const transformedUsers = users.map(user => user.transform()); + const transformedUsers = users.map((user) => user.transform()); res.json(transformedUsers); } catch (error) { next(error); @@ -96,7 +96,8 @@ exports.list = async (req, res, next) => { exports.remove = (req, res, next) => { const { user } = req.locals; - user.remove() + user + .remove() .then(() => res.status(httpStatus.NO_CONTENT).end()) - .catch(e => next(e)); + .catch((e) => next(e)); }; diff --git a/src/api/middlewares/auth.js b/src/api/middlewares/auth.js index bbb24b0..52e3a44 100644 --- a/src/api/middlewares/auth.js +++ b/src/api/middlewares/auth.js @@ -44,11 +44,13 @@ const handleJWT = (req, res, next, roles) => async (err, user, info) => { exports.ADMIN = ADMIN; exports.LOGGED_USER = LOGGED_USER; -exports.authorize = (roles = User.roles) => (req, res, next) => - passport.authenticate( - 'jwt', { session: false }, - handleJWT(req, res, next, roles), - )(req, res, next); - -exports.oAuth = service => - passport.authenticate(service, { session: false }); +exports.authorize = + (roles = User.roles) => + (req, res, next) => + passport.authenticate( + 'jwt', + { session: false }, + handleJWT(req, res, next, roles), + )(req, res, next); + +exports.oAuth = (service) => passport.authenticate(service, { session: false }); diff --git a/src/api/models/passwordResetToken.model.js b/src/api/models/passwordResetToken.model.js index 78b1912..3c4e99c 100644 --- a/src/api/models/passwordResetToken.model.js +++ b/src/api/models/passwordResetToken.model.js @@ -36,9 +36,7 @@ passwordResetTokenSchema.statics = { const userId = user._id; const userEmail = user.email; const resetToken = `${userId}.${crypto.randomBytes(40).toString('hex')}`; - const expires = moment() - .add(2, 'hours') - .toDate(); + const expires = moment().add(2, 'hours').toDate(); const ResetTokenObject = new PasswordResetToken({ resetToken, userId, @@ -53,5 +51,8 @@ passwordResetTokenSchema.statics = { /** * @typedef RefreshToken */ -const PasswordResetToken = mongoose.model('PasswordResetToken', passwordResetTokenSchema); +const PasswordResetToken = mongoose.model( + 'PasswordResetToken', + passwordResetTokenSchema, +); module.exports = PasswordResetToken; diff --git a/src/api/models/refreshToken.model.js b/src/api/models/refreshToken.model.js index 08fc70b..ebf3c2a 100644 --- a/src/api/models/refreshToken.model.js +++ b/src/api/models/refreshToken.model.js @@ -26,7 +26,6 @@ const refreshTokenSchema = new mongoose.Schema({ }); refreshTokenSchema.statics = { - /** * Generate a refresh token object and saves it into the database * @@ -39,12 +38,14 @@ refreshTokenSchema.statics = { const token = `${userId}.${crypto.randomBytes(40).toString('hex')}`; const expires = moment().add(30, 'days').toDate(); const tokenObject = new RefreshToken({ - token, userId, userEmail, expires, + token, + userId, + userEmail, + expires, }); tokenObject.save(); return tokenObject; }, - }; /** diff --git a/src/api/models/user.model.js b/src/api/models/user.model.js index e195957..22fc41b 100644 --- a/src/api/models/user.model.js +++ b/src/api/models/user.model.js @@ -9,51 +9,54 @@ const APIError = require('../utils/APIError'); const { env, jwtSecret, jwtExpirationInterval } = require('../../config/vars'); /** -* User Roles -*/ + * User Roles + */ const roles = ['user', 'admin']; /** * User Schema * @private */ -const userSchema = new mongoose.Schema({ - email: { - type: String, - match: /^\S+@\S+\.\S+$/, - required: true, - unique: true, - trim: true, - lowercase: true, - }, - password: { - type: String, - required: true, - minlength: 6, - maxlength: 128, - }, - name: { - type: String, - maxlength: 128, - index: true, - trim: true, - }, - services: { - facebook: String, - google: String, - }, - role: { - type: String, - enum: roles, - default: 'user', +const userSchema = new mongoose.Schema( + { + email: { + type: String, + match: /^\S+@\S+\.\S+$/, + required: true, + unique: true, + trim: true, + lowercase: true, + }, + password: { + type: String, + required: true, + minlength: 6, + maxlength: 128, + }, + name: { + type: String, + maxlength: 128, + index: true, + trim: true, + }, + services: { + facebook: String, + google: String, + }, + role: { + type: String, + enum: roles, + default: 'user', + }, + picture: { + type: String, + trim: true, + }, }, - picture: { - type: String, - trim: true, + { + timestamps: true, }, -}, { - timestamps: true, -}); +); /** * Add your @@ -109,7 +112,6 @@ userSchema.method({ * Statics */ userSchema.statics = { - roles, /** @@ -146,7 +148,10 @@ userSchema.statics = { */ async findAndGenerateToken(options) { const { email, password, refreshObject } = options; - if (!email) throw new APIError({ message: 'An email is required to generate a token' }); + if (!email) + throw new APIError({ + message: 'An email is required to generate a token', + }); const user = await this.findOne({ email }).exec(); const err = { @@ -154,7 +159,7 @@ userSchema.statics = { isPublic: true, }; if (password) { - if (user && await user.passwordMatches(password)) { + if (user && (await user.passwordMatches(password))) { return { user, accessToken: user.token() }; } err.message = 'Incorrect email or password'; @@ -177,9 +182,7 @@ userSchema.statics = { * @param {number} limit - Limit number of users to be returned. * @returns {Promise} */ - list({ - page = 1, perPage = 30, name, email, role, - }) { + list({ page = 1, perPage = 30, name, email, role }) { const options = omitBy({ name, email, role }, isNil); return this.find(options) @@ -200,11 +203,13 @@ userSchema.statics = { if (error.name === 'MongoError' && error.code === 11000) { return new APIError({ message: 'Validation Error', - errors: [{ - field: 'email', - location: 'body', - messages: ['"email" already exists'], - }], + errors: [ + { + field: 'email', + location: 'body', + messages: ['"email" already exists'], + }, + ], status: httpStatus.CONFLICT, isPublic: true, stack: error.stack, @@ -213,10 +218,10 @@ userSchema.statics = { return error; }, - async oAuthLogin({ - service, id, email, name, picture, - }) { - const user = await this.findOne({ $or: [{ [`services.${service}`]: id }, { email }] }); + async oAuthLogin({ service, id, email, name, picture }) { + const user = await this.findOne({ + $or: [{ [`services.${service}`]: id }, { email }], + }); if (user) { user.services[service] = id; if (!user.name) user.name = name; @@ -225,7 +230,11 @@ userSchema.statics = { } const password = uuidv4(); return this.create({ - services: { [service]: id }, email, password, name, picture, + services: { [service]: id }, + email, + password, + name, + picture, }); }, }; diff --git a/src/api/routes/v1/auth.route.js b/src/api/routes/v1/auth.route.js index a339268..831adb2 100644 --- a/src/api/routes/v1/auth.route.js +++ b/src/api/routes/v1/auth.route.js @@ -1,5 +1,5 @@ const express = require('express'); -const { validate, ValidationError, Joi } = require('express-validation') +const { validate, ValidationError, Joi } = require('express-validation'); const controller = require('../../controllers/auth.controller'); const oAuthLogin = require('../../middlewares/auth').oAuth; const { @@ -40,9 +40,7 @@ const router = express.Router(); * * @apiError (Bad Request 400) ValidationError Some parameters may contain invalid values */ -router.route('/register') - .post(validate(register), controller.register); - +router.route('/register').post(validate(register), controller.register); /** * @api {post} v1/auth/login Login @@ -71,9 +69,7 @@ router.route('/register') * @apiError (Bad Request 400) ValidationError Some parameters may contain invalid values * @apiError (Unauthorized 401) Unauthorized Incorrect email or password */ -router.route('/login') - .post(validate(login), controller.login); - +router.route('/login').post(validate(login), controller.login); /** * @api {post} v1/auth/refresh-token Refresh Token @@ -94,14 +90,14 @@ router.route('/login') * @apiError (Bad Request 400) ValidationError Some parameters may contain invalid values * @apiError (Unauthorized 401) Unauthorized Incorrect email or refreshToken */ -router.route('/refresh-token') - .post(validate(refresh), controller.refresh); +router.route('/refresh-token').post(validate(refresh), controller.refresh); - -router.route('/send-password-reset') +router + .route('/send-password-reset') .post(validate(sendPasswordReset), controller.sendPasswordReset); -router.route('/reset-password') +router + .route('/reset-password') .post(validate(passwordReset), controller.resetPassword); /** @@ -122,7 +118,8 @@ router.route('/reset-password') * @apiError (Bad Request 400) ValidationError Some parameters may contain invalid values * @apiError (Unauthorized 401) Unauthorized Incorrect access_token */ -router.route('/facebook') +router + .route('/facebook') .post(validate(oAuth), oAuthLogin('facebook'), controller.oAuth); /** @@ -143,8 +140,8 @@ router.route('/facebook') * @apiError (Bad Request 400) ValidationError Some parameters may contain invalid values * @apiError (Unauthorized 401) Unauthorized Incorrect access_token */ -router.route('/google') +router + .route('/google') .post(validate(oAuth), oAuthLogin('google'), controller.oAuth); - module.exports = router; diff --git a/src/api/routes/v1/user.route.js b/src/api/routes/v1/user.route.js index f57d830..fc3489f 100644 --- a/src/api/routes/v1/user.route.js +++ b/src/api/routes/v1/user.route.js @@ -1,5 +1,5 @@ const express = require('express'); -const { validate, ValidationError, Joi } = require('express-validation') +const { validate, ValidationError, Joi } = require('express-validation'); const controller = require('../../controllers/user.controller'); const { authorize, ADMIN, LOGGED_USER } = require('../../middlewares/auth'); const { @@ -16,7 +16,6 @@ const router = express.Router(); */ router.param('userId', controller.load); - router .route('/') /** @@ -68,7 +67,6 @@ router */ .post(authorize(ADMIN), validate(createUser), controller.create); - router .route('/profile') /** @@ -91,7 +89,6 @@ router */ .get(authorize(), controller.loggedIn); - router .route('/:userId') /** @@ -189,5 +186,4 @@ router */ .delete(authorize(LOGGED_USER), controller.remove); - module.exports = router; diff --git a/src/api/services/authProviders.js b/src/api/services/authProviders.js index d4abdbe..f67ad0a 100644 --- a/src/api/services/authProviders.js +++ b/src/api/services/authProviders.js @@ -6,9 +6,7 @@ exports.facebook = async (access_token) => { const url = 'https://graph.facebook.com/me'; const params = { access_token, fields }; const response = await axios.get(url, { params }); - const { - id, name, email, picture, - } = response.data; + const { id, name, email, picture } = response.data; return { service: 'facebook', picture: picture.data.url, @@ -22,9 +20,7 @@ exports.google = async (access_token) => { const url = 'https://www.googleapis.com/oauth2/v3/userinfo'; const params = { access_token }; const response = await axios.get(url, { params }); - const { - sub, name, email, picture, - } = response.data; + const { sub, name, email, picture } = response.data; return { service: 'google', picture, diff --git a/src/api/tests/integration/auth.test.js b/src/api/tests/integration/auth.test.js index 3a24c85..b01817d 100644 --- a/src/api/tests/integration/auth.test.js +++ b/src/api/tests/integration/auth.test.js @@ -49,9 +49,7 @@ describe('Authentication API', () => { '5947397b323ae82d8c3a333b.c69d0435e62c9f4953af912442a3d064e20291f0d228c0552ed4be473e7d191ba40b18c2c47e8b9d', userId: '5947397b323ae82d8c3a333b', userEmail: dbUser.email, - expires: moment() - .add(1, 'day') - .toDate(), + expires: moment().add(1, 'day').toDate(), }; resetToken = { @@ -59,9 +57,7 @@ describe('Authentication API', () => { '5947397b323ae82d8c3a333b.c69d0435e62c9f4953af912442a3d064e20291f0d228c0552ed4be473e7d191ba40b18c2c47e8b9d', userId: '5947397b323ae82d8c3a333b', userEmail: dbUser.email, - expires: moment() - .add(2, 'hours') - .toDate(), + expires: moment().add(2, 'hours').toDate(), }; expiredRefreshToken = { @@ -69,9 +65,7 @@ describe('Authentication API', () => { '5947397b323ae82d8c3a333b.c69d0435e62c9f4953af912442a3d064e20291f0d228c0552ed4be473e7d191ba40b18c2c47e8b9d', userId: '5947397b323ae82d8c3a333b', userEmail: dbUser.email, - expires: moment() - .subtract(1, 'day') - .toDate(), + expires: moment().subtract(1, 'day').toDate(), }; expiredResetToken = { @@ -79,9 +73,7 @@ describe('Authentication API', () => { '5947397b323ae82d8c3a333b.c69d0435e62c9f4953af912442a3d064e20291f0d228c0552ed4be473e7d191ba40b18c2c47e8b9d', userId: '5947397b323ae82d8c3a333b', userEmail: dbUser.email, - expires: moment() - .subtract(2, 'hours') - .toDate(), + expires: moment().subtract(2, 'hours').toDate(), }; await User.deleteMany({}); @@ -373,12 +365,16 @@ describe('Authentication API', () => { it('should send an email with password reset link when email matches a user', async () => { const PasswordResetTokenObj = await PasswordResetToken.create(resetToken); - expect(PasswordResetTokenObj.resetToken).to.be.equal('5947397b323ae82d8c3a333b.c69d0435e62c9f4953af912442a3d064e20291f0d228c0552ed4be473e7d191ba40b18c2c47e8b9d'); - expect(PasswordResetTokenObj.userId.toString()).to.be.equal('5947397b323ae82d8c3a333b'); + expect(PasswordResetTokenObj.resetToken).to.be.equal( + '5947397b323ae82d8c3a333b.c69d0435e62c9f4953af912442a3d064e20291f0d228c0552ed4be473e7d191ba40b18c2c47e8b9d', + ); + expect(PasswordResetTokenObj.userId.toString()).to.be.equal( + '5947397b323ae82d8c3a333b', + ); expect(PasswordResetTokenObj.userEmail).to.be.equal(dbUser.email); - expect(PasswordResetTokenObj.expires).to.be.above(moment() - .add(1, 'hour') - .toDate()); + expect(PasswordResetTokenObj.expires).to.be.above( + moment().add(1, 'hour').toDate(), + ); sandbox .stub(emailProvider, 'sendPasswordReset') @@ -463,7 +459,10 @@ describe('Authentication API', () => { it('should report error when email is not provided', () => { return request(app) .post('/v1/auth/reset-password') - .send({ password: 'updatedPassword', resetToken: resetToken.resetToken }) + .send({ + password: 'updatedPassword', + resetToken: resetToken.resetToken, + }) .expect(httpStatus.BAD_REQUEST) .then((res) => { const field1 = res.body.errors[0].field; @@ -504,12 +503,19 @@ describe('Authentication API', () => { }); it('should report error when the resetToken is expired', async () => { - const expiredPasswordResetTokenObj = await PasswordResetToken.create(expiredResetToken); + const expiredPasswordResetTokenObj = + await PasswordResetToken.create(expiredResetToken); - expect(expiredPasswordResetTokenObj.resetToken).to.be.equal('5947397b323ae82d8c3a333b.c69d0435e62c9f4953af912442a3d064e20291f0d228c0552ed4be473e7d191ba40b18c2c47e8b9d'); - expect(expiredPasswordResetTokenObj.userId.toString()).to.be.equal('5947397b323ae82d8c3a333b'); + expect(expiredPasswordResetTokenObj.resetToken).to.be.equal( + '5947397b323ae82d8c3a333b.c69d0435e62c9f4953af912442a3d064e20291f0d228c0552ed4be473e7d191ba40b18c2c47e8b9d', + ); + expect(expiredPasswordResetTokenObj.userId.toString()).to.be.equal( + '5947397b323ae82d8c3a333b', + ); expect(expiredPasswordResetTokenObj.userEmail).to.be.equal(dbUser.email); - expect(expiredPasswordResetTokenObj.expires).to.be.below(moment().toDate()); + expect(expiredPasswordResetTokenObj.expires).to.be.below( + moment().toDate(), + ); return request(app) .post('/v1/auth/reset-password') diff --git a/src/api/tests/integration/user.test.js b/src/api/tests/integration/user.test.js index 7611cb4..056b10e 100644 --- a/src/api/tests/integration/user.test.js +++ b/src/api/tests/integration/user.test.js @@ -69,8 +69,10 @@ describe('Users API', async () => { await User.insertMany([dbUsers.branStark, dbUsers.jonSnow]); dbUsers.branStark.password = password; dbUsers.jonSnow.password = password; - adminAccessToken = (await User.findAndGenerateToken(dbUsers.branStark)).accessToken; - userAccessToken = (await User.findAndGenerateToken(dbUsers.jonSnow)).accessToken; + adminAccessToken = (await User.findAndGenerateToken(dbUsers.branStark)) + .accessToken; + userAccessToken = (await User.findAndGenerateToken(dbUsers.jonSnow)) + .accessToken; }); describe('POST /v1/users', () => { @@ -147,7 +149,9 @@ describe('Users API', async () => { const { messages } = res.body.errors[0]; expect(field).to.be.equal('password'); expect(location).to.be.equal('body'); - expect(messages).to.include('"password" length must be at least 6 characters long'); + expect(messages).to.include( + '"password" length must be at least 6 characters long', + ); }); }); @@ -224,7 +228,7 @@ describe('Users API', async () => { }); }); - it('should report error when pagination\'s parameters are not a number', () => { + it("should report error when pagination's parameters are not a number", () => { return request(app) .get('/v1/users') .set('Authorization', `Bearer ${adminAccessToken}`) @@ -362,7 +366,9 @@ describe('Users API', async () => { const { messages } = res.body.errors[0]; expect(field).to.be.equal('password'); expect(location).to.be.equal('body'); - expect(messages).to.include('"password" length must be at least 6 characters long'); + expect(messages).to.include( + '"password" length must be at least 6 characters long', + ); }); }); @@ -516,7 +522,7 @@ describe('Users API', async () => { }); describe('GET /v1/users/profile', () => { - it('should get the logged user\'s info', () => { + it("should get the logged user's info", () => { delete dbUsers.jonSnow.password; return request(app) @@ -531,10 +537,12 @@ describe('Users API', async () => { it('should report error without stacktrace when accessToken is expired', async () => { // fake time const clock = sinon.useFakeTimers(); - const expiredAccessToken = (await User.findAndGenerateToken(dbUsers.branStark)).accessToken; + const expiredAccessToken = ( + await User.findAndGenerateToken(dbUsers.branStark) + ).accessToken; // move clock forward by minutes set in config + 1 minute - clock.tick((JWT_EXPIRATION * 60000) + 60000); + clock.tick(JWT_EXPIRATION * 60000 + 60000); return request(app) .get('/v1/users/profile') diff --git a/src/api/utils/APIError.js b/src/api/utils/APIError.js index bf83a15..0536356 100644 --- a/src/api/utils/APIError.js +++ b/src/api/utils/APIError.js @@ -4,9 +4,7 @@ const httpStatus = require('http-status'); * @extends Error */ class ExtendableError extends Error { - constructor({ - message, errors, status, isPublic, stack, - }) { + constructor({ message, errors, status, isPublic, stack }) { super(message); this.name = this.constructor.name; this.message = message; @@ -38,7 +36,11 @@ class APIError extends ExtendableError { isPublic = false, }) { super({ - message, errors, status, isPublic, stack, + message, + errors, + status, + isPublic, + stack, }); } } diff --git a/src/api/utils/passwordResetEmailTemplate.html b/src/api/utils/passwordResetEmailTemplate.html index 71cbfae..9b123ab 100644 --- a/src/api/utils/passwordResetEmailTemplate.html +++ b/src/api/utils/passwordResetEmailTemplate.html @@ -1,584 +1,848 @@ - + - - + - - + + - - - - - - - - + + + + + + + + - - - \ No newline at end of file + + diff --git a/src/api/validations/auth.validation.js b/src/api/validations/auth.validation.js index 73679fb..db0be56 100644 --- a/src/api/validations/auth.validation.js +++ b/src/api/validations/auth.validation.js @@ -4,25 +4,16 @@ module.exports = { // POST /v1/auth/register register: { body: Joi.object({ - email: Joi.string() - .email() - .required(), - password: Joi.string() - .required() - .min(6) - .max(128), + email: Joi.string().email().required(), + password: Joi.string().required().min(6).max(128), }), }, // POST /v1/auth/login login: { body: Joi.object({ - email: Joi.string() - .email() - .required(), - password: Joi.string() - .required() - .max(128), + email: Joi.string().email().required(), + password: Joi.string().required().max(128), }), }, @@ -37,9 +28,7 @@ module.exports = { // POST /v1/auth/refresh refresh: { body: Joi.object({ - email: Joi.string() - .email() - .required(), + email: Joi.string().email().required(), refreshToken: Joi.string().required(), }), }, @@ -47,22 +36,15 @@ module.exports = { // POST /v1/auth/refresh sendPasswordReset: { body: Joi.object({ - email: Joi.string() - .email() - .required(), + email: Joi.string().email().required(), }), }, // POST /v1/auth/password-reset passwordReset: { body: Joi.object({ - email: Joi.string() - .email() - .required(), - password: Joi.string() - .required() - .min(6) - .max(128), + email: Joi.string().email().required(), + password: Joi.string().required().min(6).max(128), resetToken: Joi.string().required(), }), }, diff --git a/src/api/validations/user.validation.js b/src/api/validations/user.validation.js index 95abbe9..92fb707 100644 --- a/src/api/validations/user.validation.js +++ b/src/api/validations/user.validation.js @@ -2,7 +2,6 @@ const Joi = require('joi'); const User = require('../models/user.model'); module.exports = { - // GET /v1/users listUsers: { query: Joi.object({ @@ -20,7 +19,7 @@ module.exports = { email: Joi.string().email().required(), password: Joi.string().min(6).max(128).required(), name: Joi.string().max(128), - role: Joi.string() + role: Joi.string(), }), }, @@ -30,10 +29,12 @@ module.exports = { email: Joi.string().email().required(), password: Joi.string().min(6).max(128).required(), name: Joi.string().max(128), - role: Joi.string() + role: Joi.string(), }), params: { - userId: Joi.string().regex(/^[a-fA-F0-9]{24}$/).required(), + userId: Joi.string() + .regex(/^[a-fA-F0-9]{24}$/) + .required(), }, }, @@ -46,7 +47,9 @@ module.exports = { role: Joi.string(), }), params: { - userId: Joi.string().regex(/^[a-fA-F0-9]{24}$/).required(), + userId: Joi.string() + .regex(/^[a-fA-F0-9]{24}$/) + .required(), }, }, }; diff --git a/src/config/express.js b/src/config/express.js index a5ab6c7..7718862 100644 --- a/src/config/express.js +++ b/src/config/express.js @@ -12,9 +12,9 @@ const strategies = require('./passport'); const error = require('../api/middlewares/error'); /** -* Express instance -* @public -*/ + * Express instance + * @public + */ const app = express(); // request logging. dev: console | production: file diff --git a/src/config/logger.js b/src/config/logger.js index 38ad8e9..477f2c2 100644 --- a/src/config/logger.js +++ b/src/config/logger.js @@ -18,9 +18,11 @@ const logger = winston.createLogger({ // `${info.level}: ${info.message} JSON.stringify({ ...rest }) ` // if (process.env.NODE_ENV !== 'production') { - logger.add(new winston.transports.Console({ - format: winston.format.simple(), - })); + logger.add( + new winston.transports.Console({ + format: winston.format.simple(), + }), + ); } logger.stream = { diff --git a/src/config/passport.js b/src/config/passport.js index 8a07383..8fcb17c 100644 --- a/src/config/passport.js +++ b/src/config/passport.js @@ -20,7 +20,7 @@ const jwt = async (payload, done) => { } }; -const oAuth = service => async (token, done) => { +const oAuth = (service) => async (token, done) => { try { const userData = await authProviders[service](token); const user = await User.oAuthLogin(userData); diff --git a/src/config/vars.js b/src/config/vars.js index d3b7da5..56d29cd 100644 --- a/src/config/vars.js +++ b/src/config/vars.js @@ -1,8 +1,8 @@ const path = require('path'); // import .env variables -require('dotenv-safe').load({ - path: path.join(__dirname, '../../.env'), +require('dotenv').config({ + path: path.join(__dirname, `../../.env.${process.env.NODE_ENV}`), sample: path.join(__dirname, '../../.env.example'), }); @@ -12,7 +12,10 @@ module.exports = { jwtSecret: process.env.JWT_SECRET, jwtExpirationInterval: process.env.JWT_EXPIRATION_MINUTES, mongo: { - uri: process.env.NODE_ENV === 'test' ? process.env.MONGO_URI_TESTS : process.env.MONGO_URI, + uri: + process.env.NODE_ENV === 'test' + ? process.env.MONGO_URI_TESTS + : process.env.MONGO_URI, }, logs: process.env.NODE_ENV === 'production' ? 'combined' : 'dev', emailConfig: { diff --git a/src/index.js b/src/index.js index 982fc26..12c2133 100644 --- a/src/index.js +++ b/src/index.js @@ -10,7 +10,7 @@ mongoose.connect(); app.listen(port, () => logger.info(`server started on port ${port} (${env})`)); /** -* Exports express -* @public -*/ + * Exports express + * @public + */ module.exports = app; diff --git a/yarn.lock b/yarn.lock index 5e1f466..47f6d89 100644 --- a/yarn.lock +++ b/yarn.lock @@ -218,14 +218,14 @@ resolved "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz" integrity sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q== -"@eslint-community/eslint-utils@^4.2.0": +"@eslint-community/eslint-utils@^4.1.2", "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz" integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== dependencies: eslint-visitor-keys "^3.3.0" -"@eslint-community/regexpp@^4.6.1": +"@eslint-community/regexpp@^4.6.0", "@eslint-community/regexpp@^4.6.1": version "4.10.0" resolved "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz" integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== @@ -478,6 +478,11 @@ "@opencensus/core" "^0.0.8" uuid "^3.2.1" +"@pkgr/core@^0.1.0": + version "0.1.1" + resolved "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz" + integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA== + "@pm2/agent@~2.0.0": version "2.0.3" resolved "https://registry.npmjs.org/@pm2/agent/-/agent-2.0.3.tgz" @@ -619,7 +624,7 @@ "@types/eslint" "*" "@types/estree" "*" -"@types/eslint@*": +"@types/eslint@*", "@types/eslint@>=8.0.0": version "8.56.5" resolved "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.5.tgz" integrity sha512-u5/YPJHo1tvkSF2CE0USEkxon82Z5DBy2xR+qfyYNszpX9qcs4sT6uq2kBbj4BXY1+DBGDPnrhMZV3pKWGNukw== @@ -1394,6 +1399,18 @@ buffer-from@^1.0.0: resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +builtin-modules@^3.3.0: + version "3.3.0" + resolved "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz" + integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== + +builtins@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz" + integrity sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ== + dependencies: + semver "^7.0.0" + bytes@3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz" @@ -1760,11 +1777,6 @@ concat-map@0.0.1: resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -confusing-browser-globals@^1.0.10: - version "1.0.11" - resolved "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz" - integrity sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA== - constantinople@^4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz" @@ -1847,13 +1859,6 @@ croner@~4.1.92: resolved "https://registry.npmjs.org/croner/-/croner-4.1.97.tgz" integrity sha512-/f6gpQuxDaqXu+1kwQYSckUglPaOrHdbIlBAu0YuW8/Cdb45XwXYNUBXg3r/9Mo6n540Kn/smKcZWko5x99KrQ== -cross-env@^7.0.3: - version "7.0.3" - resolved "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz" - integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== - dependencies: - cross-spawn "^7.0.1" - cross-spawn@^6.0.0: version "6.0.5" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz" @@ -1865,7 +1870,7 @@ cross-spawn@^6.0.0: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: +cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -2240,7 +2245,22 @@ domutils@^3.0.1: domelementtype "^2.3.0" domhandler "^5.0.3" -dotenv@^16.4.5: +dotenv-cli@^7.4.1: + version "7.4.1" + resolved "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-7.4.1.tgz" + integrity sha512-fE1aywjRrWGxV3miaiUr3d2zC/VAiuzEGghi+QzgIA9fEf/M5hLMaRSXb4IxbUAwGmaLi0IozdZddnVU96acag== + dependencies: + cross-spawn "^7.0.3" + dotenv "^16.3.0" + dotenv-expand "^10.0.0" + minimist "^1.2.6" + +dotenv-expand@^10.0.0: + version "10.0.0" + resolved "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz" + integrity sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A== + +dotenv@^16.3.0, dotenv@^16.4.5: version "16.4.5" resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz" integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== @@ -2538,15 +2558,15 @@ escodegen@^2.1.0: optionalDependencies: source-map "~0.6.1" -eslint-config-airbnb-base@^15.0.0: - version "15.0.0" - resolved "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz" - integrity sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig== - dependencies: - confusing-browser-globals "^1.0.10" - object.assign "^4.1.2" - object.entries "^1.1.5" - semver "^6.3.0" +eslint-compat-utils@^0.1.2: + version "0.1.2" + resolved "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.1.2.tgz" + integrity sha512-Jia4JDldWnFNIru1Ehx1H5s9/yxiRHY/TimCuUc0jNexew3cF1gI6CYZil1ociakfWO3rRqFjl1mskBblB3RYg== + +eslint-config-standard@^17.1.0: + version "17.1.0" + resolved "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz" + integrity sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q== eslint-import-resolver-node@^0.3.9: version "0.3.9" @@ -2564,7 +2584,16 @@ eslint-module-utils@^2.8.0: dependencies: debug "^3.2.7" -eslint-plugin-import@^2.25.2, eslint-plugin-import@^2.25.3: +eslint-plugin-es-x@^7.5.0: + version "7.5.0" + resolved "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.5.0.tgz" + integrity sha512-ODswlDSO0HJDzXU0XvgZ3lF3lS3XAZEossh15Q2UHjwrJggWeBoKqqEsLTZLXl+dh5eOAozG0zRcYtuE35oTuQ== + dependencies: + "@eslint-community/eslint-utils" "^4.1.2" + "@eslint-community/regexpp" "^4.6.0" + eslint-compat-utils "^0.1.2" + +eslint-plugin-import@^2.25.2, eslint-plugin-import@^2.29.1: version "2.29.1" resolved "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz" integrity sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw== @@ -2587,6 +2616,36 @@ eslint-plugin-import@^2.25.2, eslint-plugin-import@^2.25.3: semver "^6.3.1" tsconfig-paths "^3.15.0" +"eslint-plugin-n@^15.0.0 || ^16.0.0 ", eslint-plugin-n@^16.6.2: + version "16.6.2" + resolved "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.6.2.tgz" + integrity sha512-6TyDmZ1HXoFQXnhCTUjVFULReoBPOAjpuiKELMkeP40yffI/1ZRO+d9ug/VC6fqISo2WkuIBk3cvuRPALaWlOQ== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + builtins "^5.0.1" + eslint-plugin-es-x "^7.5.0" + get-tsconfig "^4.7.0" + globals "^13.24.0" + ignore "^5.2.4" + is-builtin-module "^3.2.1" + is-core-module "^2.12.1" + minimatch "^3.1.2" + resolve "^1.22.2" + semver "^7.5.3" + +eslint-plugin-prettier@^5.1.3: + version "5.1.3" + resolved "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz" + integrity sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw== + dependencies: + prettier-linter-helpers "^1.0.0" + synckit "^0.8.6" + +eslint-plugin-promise@^6.0.0, eslint-plugin-promise@^6.1.1: + version "6.1.1" + resolved "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz" + integrity sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig== + eslint-scope@^7.2.2: version "7.2.2" resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz" @@ -2608,7 +2667,7 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4 resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -"eslint@^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^7.32.0 || ^8.2.0", eslint@^8.5.0: +"eslint@^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^7.0.0 || ^8.0.0", eslint@^8.0.1, eslint@^8.57.0, eslint@>=6.0.0, eslint@>=7.0.0, eslint@>=8, eslint@>=8.0.0: version "8.57.0" resolved "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz" integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== @@ -2821,6 +2880,11 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== +fast-diff@^1.1.2: + version "1.3.0" + resolved "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz" + integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== + fast-json-patch@^3.0.0-1: version "3.1.1" resolved "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz" @@ -3151,6 +3215,13 @@ get-symbol-description@^1.0.2: es-errors "^1.3.0" get-intrinsic "^1.2.4" +get-tsconfig@^4.7.0: + version "4.7.3" + resolved "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.3.tgz" + integrity sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg== + dependencies: + resolve-pkg-maps "^1.0.0" + get-uri@^6.0.1: version "6.0.3" resolved "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz" @@ -3225,7 +3296,7 @@ globals@^11.1.0: resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globals@^13.19.0: +globals@^13.19.0, globals@^13.24.0: version "13.24.0" resolved "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz" integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== @@ -3482,7 +3553,7 @@ ignore-by-default@^1.0.1: resolved "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz" integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== -ignore@^5.2.0: +ignore@^5.2.0, ignore@^5.2.4: version "5.3.1" resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz" integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== @@ -3603,12 +3674,19 @@ is-boolean-object@^1.1.0: call-bind "^1.0.2" has-tostringtag "^1.0.0" +is-builtin-module@^3.2.1: + version "3.2.1" + resolved "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz" + integrity sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A== + dependencies: + builtin-modules "^3.3.0" + is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: version "1.2.7" resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz" integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== -is-core-module@^2.13.0, is-core-module@^2.13.1: +is-core-module@^2.12.1, is-core-module@^2.13.0, is-core-module@^2.13.1: version "2.13.1" resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz" integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== @@ -4557,6 +4635,18 @@ module-details-from-path@^1.0.3: resolved "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz" integrity sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A== +moment-timezone@^0.5.45: + version "0.5.45" + resolved "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.45.tgz" + integrity sha512-HIWmqA86KcmCAhnMAN0wuDOARV/525R2+lOLotuGFzn4HO+FH+/645z2wx0Dt3iDv6/p61SIvKnDstISainhLQ== + dependencies: + moment "^2.29.4" + +moment@^2.29.4: + version "2.30.1" + resolved "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz" + integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how== + mongodb-connection-string-url@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.0.tgz" @@ -4859,7 +4949,7 @@ object-keys@^1.1.1: resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object.assign@^4.1.2, object.assign@^4.1.5: +object.assign@^4.1.5: version "4.1.5" resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz" integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== @@ -4869,15 +4959,6 @@ object.assign@^4.1.2, object.assign@^4.1.5: has-symbols "^1.0.3" object-keys "^1.1.1" -object.entries@^1.1.5: - version "1.1.7" - resolved "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz" - integrity sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - object.fromentries@^2.0.7: version "2.0.7" resolved "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz" @@ -5375,6 +5456,18 @@ prelude-ls@^1.2.1: resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier@^3.2.5, prettier@>=3.0.0: + version "3.2.5" + resolved "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz" + integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A== + preview-email@^3.0.17: version "3.0.19" resolved "https://registry.npmjs.org/preview-email/-/preview-email-3.0.19.tgz" @@ -5761,7 +5854,12 @@ resolve-from@^5.0.0: resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== -resolve@^1.10.0, resolve@^1.15.1, resolve@^1.22.1, resolve@^1.22.4, resolve@^1.9.0: +resolve-pkg-maps@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz" + integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== + +resolve@^1.10.0, resolve@^1.15.1, resolve@^1.22.1, resolve@^1.22.2, resolve@^1.22.4, resolve@^1.9.0: version "1.22.8" resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz" integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== @@ -5906,7 +6004,7 @@ semver@^6.3.1: resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.2, semver@^7.3.5, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4: +semver@^7.0.0, semver@^7.2, semver@^7.3.5, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4: version "7.6.0" resolved "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz" integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== @@ -6385,6 +6483,14 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +synckit@^0.8.6: + version "0.8.8" + resolved "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz" + integrity sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ== + dependencies: + "@pkgr/core" "^0.1.0" + tslib "^2.6.2" + systeminformation@^5.7: version "5.22.0" resolved "https://registry.npmjs.org/systeminformation/-/systeminformation-5.22.0.tgz" @@ -6535,7 +6641,7 @@ tsconfig-paths@^3.15.0: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@^2.0.1: +tslib@^2.0.1, tslib@^2.6.2: version "2.6.2" resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==