diff --git a/.eslintrc.js b/.eslintrc.js index 64d1185609d01284db3b890e0de72931ecf2d0f5..6110d3e9c02a4a9798dcc9d66a3dc8ff99ffaa4f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,25 +1,45 @@ module.exports = { - parser: '@typescript-eslint/parser', - parserOptions: { - project: 'tsconfig.json', - sourceType: 'module', - }, - plugins: ['@typescript-eslint/eslint-plugin'], - extends: [ - 'plugin:@typescript-eslint/recommended', - 'prettier/@typescript-eslint', - 'plugin:prettier/recommended', - ], - root: true, - env: { - node: true, - jest: true, - }, - ignorePatterns: ['.eslintrc.js'], - rules: { - '@typescript-eslint/interface-name-prefix': 'off', - '@typescript-eslint/explicit-function-return-type': 'off', - '@typescript-eslint/explicit-module-boundary-types': 'off', - '@typescript-eslint/no-explicit-any': 'off', - }, + parser: '@typescript-eslint/parser', + parserOptions: { + project: 'tsconfig.json', + sourceType: 'module', + }, + plugins: ['@typescript-eslint/eslint-plugin', 'prettier'], + extends: [ + 'plugin:@typescript-eslint/eslint-recommended', + 'prettier', + 'prettier/@typescript-eslint', + 'plugin:import/errors', + 'plugin:import/warnings', + 'plugin:import/typescript', + ], + root: true, + env: { + node: true, + jest: true, + }, + rules: { + 'prettier/prettier': 2, + + '@typescript-eslint/interface-name-prefix': 0, + '@typescript-eslint/explicit-function-return-type': 0, + '@typescript-eslint/no-explicit-any': 0, + 'prefer-rest-params': 0, + + 'require-await': 2, + 'no-return-await': 2, + 'no-await-in-loop': 2, + 'no-console': 2, + curly: 2, + + '@typescript-eslint/no-unused-vars': 2, + + eqeqeq: 2, + + 'padding-line-between-statements': [ + 'error', + { blankLine: "always", prev: "block-like", next: "*" }, // after if / while, etc + { blankLine: "always", prev: "*", next: "return" }, // before return + ], + }, }; diff --git a/.prettierrc b/.prettierrc index dcb72794f5300a3e0ccd2ad0669d802b62f3d370..93f9f1ba2ef292e2650e7c9e45ed47e03edd9542 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,4 +1,6 @@ { - "singleQuote": true, - "trailingComma": "all" + "singleQuote": true, + "trailingComma": "all", + "tabWidth": 4, + "printWidth": 100 } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 07a6c8aa0ead9ce725d5d892fe811f7ae4a83255..975d275a862531e5645ba1854c1d75ec2e98a686 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,11 +9,14 @@ "license": "UNLICENSED", "dependencies": { "@nestjs/common": "^7.5.1", + "@nestjs/config": "^1.0.1", "@nestjs/core": "^7.5.1", "@nestjs/platform-express": "^7.5.1", + "@nestjs/swagger": "^4.7.12", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", - "rxjs": "^6.6.3" + "rxjs": "^6.6.3", + "swagger-ui-express": "^4.1.6" }, "devDependencies": { "@nestjs/cli": "^7.5.1", @@ -1626,6 +1629,24 @@ } } }, + "node_modules/@nestjs/config": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-1.0.1.tgz", + "integrity": "sha512-azMl4uYlFIhYsywFxPJT81RxF3Pnn0TZW3EEmr0Wa0Wex8R2xpvBNrCcrOgW3TB1xGMP7eqBrlfsVh5ZP82szg==", + "dependencies": { + "dotenv": "10.0.0", + "dotenv-expand": "5.1.0", + "lodash.get": "4.4.2", + "lodash.has": "4.5.2", + "lodash.set": "4.3.2", + "uuid": "8.3.2" + }, + "peerDependencies": { + "@nestjs/common": "^7.0.0 || ^8.0.0", + "reflect-metadata": "^0.1.13", + "rxjs": "^6.0.0 || ^7.2.0" + } + }, "node_modules/@nestjs/core": { "version": "7.6.18", "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-7.6.18.tgz", @@ -1664,6 +1685,17 @@ } } }, + "node_modules/@nestjs/mapped-types": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-0.4.1.tgz", + "integrity": "sha512-JXrw2LMangSU3vnaXWXVX47GRG1FbbNh4aVBbidDjxT3zlghsoNQY6qyWtT001MCl8lJGo8I6i6+DurBRRxl/Q==", + "peerDependencies": { + "@nestjs/common": "^7.0.8", + "class-transformer": "^0.2.0 || ^0.3.0 || ^0.4.0", + "class-validator": "^0.11.1 || ^0.12.0 || ^0.13.0", + "reflect-metadata": "^0.1.12" + } + }, "node_modules/@nestjs/platform-express": { "version": "7.6.18", "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-7.6.18.tgz", @@ -1790,6 +1822,31 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, + "node_modules/@nestjs/swagger": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-4.8.2.tgz", + "integrity": "sha512-RSUwcVxrzXF7/b/IZ5lXnYHJ6jIGS9wWRTJKIt1kIaCNWT+0wRfTlAyhQkzs2g35/PTXJEcdIwwY7mBO/bwHzw==", + "dependencies": { + "@nestjs/mapped-types": "0.4.1", + "lodash": "4.17.21", + "path-to-regexp": "3.2.0" + }, + "peerDependencies": { + "@nestjs/common": "^6.8.0 || ^7.0.0", + "@nestjs/core": "^6.8.0 || ^7.0.0", + "fastify-swagger": "*", + "reflect-metadata": "^0.1.12", + "swagger-ui-express": "*" + }, + "peerDependenciesMeta": { + "fastify-swagger": { + "optional": true + }, + "swagger-ui-express": { + "optional": true + } + } + }, "node_modules/@nestjs/testing": { "version": "7.6.18", "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-7.6.18.tgz", @@ -2174,6 +2231,12 @@ "@types/superagent": "*" } }, + "node_modules/@types/validator": { + "version": "13.6.3", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.6.3.tgz", + "integrity": "sha512-fWG42pMJOL4jKsDDZZREnXLjc3UE0R8LOJfARWYg6U966rxDT7TYejYzLnUF5cvSObGg34nd0+H2wHHU5Omdfw==", + "peer": true + }, "node_modules/@types/yargs": { "version": "15.0.14", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", @@ -3346,6 +3409,12 @@ "integrity": "sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw==", "dev": true }, + "node_modules/class-transformer": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.4.0.tgz", + "integrity": "sha512-ETWD/H2TbWbKEi7m9N4Km5+cw1hNcqJSxlSYhsLsNjQzWWiZIYA1zafxpK9PwVfaZ6AqR5rrjPVUBGESm5tQUA==", + "peer": true + }, "node_modules/class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", @@ -3444,6 +3513,17 @@ "node": ">=0.10.0" } }, + "node_modules/class-validator": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.13.1.tgz", + "integrity": "sha512-zWIeYFhUitvAHBwNhDdCRK09hWx+P0HUwFE8US8/CxFpMVzkUK8RJl7yOIE+BVu2lxyPNgeOaFv78tLE47jBIg==", + "peer": true, + "dependencies": { + "@types/validator": "^13.1.3", + "libphonenumber-js": "^1.9.7", + "validator": "^13.5.2" + } + }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -4000,6 +4080,19 @@ "node": ">=8" } }, + "node_modules/dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "engines": { + "node": ">=10" + } + }, + "node_modules/dotenv-expand": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", + "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==" + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -6997,6 +7090,12 @@ "node": ">= 0.8.0" } }, + "node_modules/libphonenumber-js": { + "version": "1.9.34", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.9.34.tgz", + "integrity": "sha512-gHTNU9xTtVgSp30IDX/57W4pETMXDIYXFfwEOJVXiYosiY7Hc7ogJwlBjOqlCcU04X0aA8DT57hdwUC1sJBJnA==", + "peer": true + }, "node_modules/lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", @@ -7041,8 +7140,7 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash.clonedeep": { "version": "4.5.0", @@ -7050,12 +7148,27 @@ "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", "dev": true }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" + }, + "node_modules/lodash.has": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz", + "integrity": "sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI=" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.set": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", + "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=" + }, "node_modules/lodash.toarray": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz", @@ -9793,6 +9906,25 @@ "node": ">=8" } }, + "node_modules/swagger-ui-dist": { + "version": "3.52.3", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.52.3.tgz", + "integrity": "sha512-7QSY4milmYx5O8dbzU5tTftiaoZt+4JGxahTTBiLAnbTvhTyzum9rsjDIJjC+xeT8Tt1KfB38UuQQjmrh2THDQ==" + }, + "node_modules/swagger-ui-express": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.1.6.tgz", + "integrity": "sha512-Xs2BGGudvDBtL7RXcYtNvHsFtP1DBFPMJFRxHe5ez/VG/rzVOEjazJOOSc/kSCyxreCTKfJrII6MJlL9a6t8vw==", + "dependencies": { + "swagger-ui-dist": "^3.18.1" + }, + "engines": { + "node": ">= v0.10.32" + }, + "peerDependencies": { + "express": ">=4.0.0" + } + }, "node_modules/symbol-observable": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-3.0.0.tgz", @@ -10639,6 +10771,15 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/validator": { + "version": "13.6.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.6.0.tgz", + "integrity": "sha512-gVgKbdbHgtxpRyR8K0O6oFZPhhB5tT1jeEHZR0Znr9Svg03U0+r9DXWMrnRAB+HtCStDQKlaIZm42tVsVjqtjg==", + "peer": true, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -12367,6 +12508,19 @@ "uuid": "8.3.2" } }, + "@nestjs/config": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-1.0.1.tgz", + "integrity": "sha512-azMl4uYlFIhYsywFxPJT81RxF3Pnn0TZW3EEmr0Wa0Wex8R2xpvBNrCcrOgW3TB1xGMP7eqBrlfsVh5ZP82szg==", + "requires": { + "dotenv": "10.0.0", + "dotenv-expand": "5.1.0", + "lodash.get": "4.4.2", + "lodash.has": "4.5.2", + "lodash.set": "4.3.2", + "uuid": "8.3.2" + } + }, "@nestjs/core": { "version": "7.6.18", "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-7.6.18.tgz", @@ -12381,6 +12535,12 @@ "uuid": "8.3.2" } }, + "@nestjs/mapped-types": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-0.4.1.tgz", + "integrity": "sha512-JXrw2LMangSU3vnaXWXVX47GRG1FbbNh4aVBbidDjxT3zlghsoNQY6qyWtT001MCl8lJGo8I6i6+DurBRRxl/Q==", + "requires": {} + }, "@nestjs/platform-express": { "version": "7.6.18", "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-7.6.18.tgz", @@ -12473,6 +12633,16 @@ } } }, + "@nestjs/swagger": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-4.8.2.tgz", + "integrity": "sha512-RSUwcVxrzXF7/b/IZ5lXnYHJ6jIGS9wWRTJKIt1kIaCNWT+0wRfTlAyhQkzs2g35/PTXJEcdIwwY7mBO/bwHzw==", + "requires": { + "@nestjs/mapped-types": "0.4.1", + "lodash": "4.17.21", + "path-to-regexp": "3.2.0" + } + }, "@nestjs/testing": { "version": "7.6.18", "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-7.6.18.tgz", @@ -12811,6 +12981,12 @@ "@types/superagent": "*" } }, + "@types/validator": { + "version": "13.6.3", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.6.3.tgz", + "integrity": "sha512-fWG42pMJOL4jKsDDZZREnXLjc3UE0R8LOJfARWYg6U966rxDT7TYejYzLnUF5cvSObGg34nd0+H2wHHU5Omdfw==", + "peer": true + }, "@types/yargs": { "version": "15.0.14", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", @@ -13697,6 +13873,12 @@ "integrity": "sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw==", "dev": true }, + "class-transformer": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.4.0.tgz", + "integrity": "sha512-ETWD/H2TbWbKEi7m9N4Km5+cw1hNcqJSxlSYhsLsNjQzWWiZIYA1zafxpK9PwVfaZ6AqR5rrjPVUBGESm5tQUA==", + "peer": true + }, "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", @@ -13777,6 +13959,17 @@ } } }, + "class-validator": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.13.1.tgz", + "integrity": "sha512-zWIeYFhUitvAHBwNhDdCRK09hWx+P0HUwFE8US8/CxFpMVzkUK8RJl7yOIE+BVu2lxyPNgeOaFv78tLE47jBIg==", + "peer": true, + "requires": { + "@types/validator": "^13.1.3", + "libphonenumber-js": "^1.9.7", + "validator": "^13.5.2" + } + }, "cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -14216,6 +14409,16 @@ } } }, + "dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==" + }, + "dotenv-expand": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", + "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==" + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -16504,6 +16707,12 @@ "type-check": "~0.4.0" } }, + "libphonenumber-js": { + "version": "1.9.34", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.9.34.tgz", + "integrity": "sha512-gHTNU9xTtVgSp30IDX/57W4pETMXDIYXFfwEOJVXiYosiY7Hc7ogJwlBjOqlCcU04X0aA8DT57hdwUC1sJBJnA==", + "peer": true + }, "lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", @@ -16539,8 +16748,7 @@ "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash.clonedeep": { "version": "4.5.0", @@ -16548,12 +16756,27 @@ "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", "dev": true }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" + }, + "lodash.has": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz", + "integrity": "sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI=" + }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "lodash.set": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", + "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=" + }, "lodash.toarray": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz", @@ -18688,6 +18911,19 @@ "supports-color": "^7.0.0" } }, + "swagger-ui-dist": { + "version": "3.52.3", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.52.3.tgz", + "integrity": "sha512-7QSY4milmYx5O8dbzU5tTftiaoZt+4JGxahTTBiLAnbTvhTyzum9rsjDIJjC+xeT8Tt1KfB38UuQQjmrh2THDQ==" + }, + "swagger-ui-express": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.1.6.tgz", + "integrity": "sha512-Xs2BGGudvDBtL7RXcYtNvHsFtP1DBFPMJFRxHe5ez/VG/rzVOEjazJOOSc/kSCyxreCTKfJrII6MJlL9a6t8vw==", + "requires": { + "swagger-ui-dist": "^3.18.1" + } + }, "symbol-observable": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-3.0.0.tgz", @@ -19323,6 +19559,12 @@ "spdx-expression-parse": "^3.0.0" } }, + "validator": { + "version": "13.6.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.6.0.tgz", + "integrity": "sha512-gVgKbdbHgtxpRyR8K0O6oFZPhhB5tT1jeEHZR0Znr9Svg03U0+r9DXWMrnRAB+HtCStDQKlaIZm42tVsVjqtjg==", + "peer": true + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index cbf20fe93a3faa68208ad4f5a32f7b78f8932959..22213e99230ce0e6fd092048e567509608a5a9f3 100644 --- a/package.json +++ b/package.json @@ -22,11 +22,14 @@ }, "dependencies": { "@nestjs/common": "^7.5.1", + "@nestjs/config": "^1.0.1", "@nestjs/core": "^7.5.1", "@nestjs/platform-express": "^7.5.1", + "@nestjs/swagger": "^4.7.12", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", - "rxjs": "^6.6.3" + "rxjs": "^6.6.3", + "swagger-ui-express": "^4.1.6" }, "devDependencies": { "@nestjs/cli": "^7.5.1", diff --git a/src/app.controller.spec.ts b/src/app.controller.spec.ts deleted file mode 100644 index d22f3890a380cea30641cfecc329b5c794ef5fb2..0000000000000000000000000000000000000000 --- a/src/app.controller.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { AppController } from './app.controller'; -import { AppService } from './app.service'; - -describe('AppController', () => { - let appController: AppController; - - beforeEach(async () => { - const app: TestingModule = await Test.createTestingModule({ - controllers: [AppController], - providers: [AppService], - }).compile(); - - appController = app.get(AppController); - }); - - describe('root', () => { - it('should return "Hello World!"', () => { - expect(appController.getHello()).toBe('Hello World!'); - }); - }); -}); diff --git a/src/app.controller.ts b/src/app.controller.ts deleted file mode 100644 index cce879ee622146012901c9adb47ef40c0fd3a555..0000000000000000000000000000000000000000 --- a/src/app.controller.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Controller, Get } from '@nestjs/common'; -import { AppService } from './app.service'; - -@Controller() -export class AppController { - constructor(private readonly appService: AppService) {} - - @Get() - getHello(): string { - return this.appService.getHello(); - } -} diff --git a/src/app.module.ts b/src/app.module.ts index 86628031ca2a10fe172fe824f69d1720c44b43ce..56cea32fbcf3e16636ef89087235132c94bfaaed 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,10 +1,13 @@ import { Module } from '@nestjs/common'; -import { AppController } from './app.controller'; -import { AppService } from './app.service'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { getConfig } from './config'; @Module({ - imports: [], - controllers: [AppController], - providers: [AppService], + imports: [ConfigModule.forRoot({ + isGlobal: true, + expandVariables: true, + load: [getConfig], + })], + providers: [ConfigService], }) export class AppModule {} diff --git a/src/app.service.ts b/src/app.service.ts deleted file mode 100644 index 927d7cca0badb13577152bf8753ce3552358f53b..0000000000000000000000000000000000000000 --- a/src/app.service.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -@Injectable() -export class AppService { - getHello(): string { - return 'Hello World!'; - } -} diff --git a/src/config/app.ts b/src/config/app.ts new file mode 100644 index 0000000000000000000000000000000000000000..083d633ee1b65f3241aafb60c28d4c87cb679d42 --- /dev/null +++ b/src/config/app.ts @@ -0,0 +1,18 @@ +export const appSchema = { + type: 'object', + properties: { + SERVICE_NAME: { + type: 'string', + default: 'ttl-lifo', + }, + API_DOC_PATH: { + type: 'string', + default: 'api/doc', + }, + API_DOC_ENABLED: { + type: ['string', 'number'], + description: 'The flag to turn on/off swagger doc api', + default: 1, + }, + }, +}; diff --git a/src/config/config.ts b/src/config/config.ts new file mode 100644 index 0000000000000000000000000000000000000000..a0a1183607740d3cfadd2a3e6de848dd05bc5ab7 --- /dev/null +++ b/src/config/config.ts @@ -0,0 +1,27 @@ +import Ajv from 'ajv'; +import { envSchema, appSchema } from './'; + +const ajv = new Ajv({ + allErrors: true, +}) + +export const getConfig = () => { + const env = process.env; + const config = { ...env }; + + const validationSchema = ajv.compile(appSchema); + const valid = ajv.validate(appSchema, config); + if (!valid) { + throw new Error(JSON.stringify(validationSchema.errors)); + } + + return config; +} + +export const initConfig = () => { + const validationSchema = ajv.compile(envSchema); + const valid = ajv.validate(appSchema, process.env); + if (!valid) { + throw new Error(JSON.stringify(validationSchema.errors)); + } +} \ No newline at end of file diff --git a/src/config/env.ts b/src/config/env.ts new file mode 100644 index 0000000000000000000000000000000000000000..3bee9665e49690142ff44523edead6c6b521dd68 --- /dev/null +++ b/src/config/env.ts @@ -0,0 +1,17 @@ +export const envSchema = { + type: 'object', + required: [ + 'PORT', + 'NODE_ENV', + ], + properties: { + PORT: { + type: ['string', 'number'], + default: 3000, + }, + NODE_ENV: { + type: 'string', + default: 'dev', + }, + }, +}; diff --git a/src/config/index.ts b/src/config/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..e405dfa2a5032c3bb842a8bb6d052b3cf15099dd --- /dev/null +++ b/src/config/index.ts @@ -0,0 +1,3 @@ +export * from './env'; +export * from './app'; +export * from './config'; diff --git a/src/filter/http-exception.ts b/src/filter/http-exception.ts new file mode 100644 index 0000000000000000000000000000000000000000..b8b55e52b30af082a363bd19b5c561a30dcc831b --- /dev/null +++ b/src/filter/http-exception.ts @@ -0,0 +1,36 @@ +import { + ExceptionFilter, + Catch, + ArgumentsHost, + HttpException, + InternalServerErrorException, +} from '@nestjs/common'; +import { Request, Response } from 'express'; +import { Reflector } from '@nestjs/core'; + +@Catch() +export class HttpExceptionFilter implements ExceptionFilter { + constructor(public reflector: Reflector) {} + + catch(error: Error, host: ArgumentsHost) { + const exception = + error instanceof HttpException + ? error + : new InternalServerErrorException('Something went wrong'); + + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + const request = ctx.getRequest(); + const status = exception.getStatus(); + + const messages = (exception as any)?.response?.message; + + response.status(status).json({ + statusCode: status, + timestamp: new Date().toISOString(), + message: exception.message, + path: request.url, + ...(messages ? { messages: messages } : {}), + }); + } +} diff --git a/src/filter/index.ts b/src/filter/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..20ba587abdadf157ee85fd9ec583402a65873054 --- /dev/null +++ b/src/filter/index.ts @@ -0,0 +1 @@ +export * from './http-exception'; diff --git a/src/main.ts b/src/main.ts index 13cad38cff92aa3b3d3ef6232306e450cadf5713..12599c209c6a14d27b695372b93aa974f91b7484 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,8 +1,46 @@ -import { NestFactory } from '@nestjs/core'; +import { NestFactory, Reflector } from '@nestjs/core'; +import { ConfigService } from '@nestjs/config'; +import { ClassSerializerInterceptor } from '@nestjs/common'; +import { ExpressAdapter, NestExpressApplication } from '@nestjs/platform-express'; + +import { initConfig } from './config'; +import { setupSwagger } from './swagger-setup'; +import { HttpExceptionFilter } from './filter'; import { AppModule } from './app.module'; async function bootstrap() { - const app = await NestFactory.create(AppModule); - await app.listen(3000); + try { + await initConfig(); + const app = await NestFactory.create( + AppModule, + new ExpressAdapter(), + { cors: true }, + ); + const configService = app.get(ConfigService); + const reflector = app.get(Reflector); + + app.useGlobalFilters(new HttpExceptionFilter(reflector)); + app.useGlobalInterceptors(new ClassSerializerInterceptor(reflector)); + + let apiDoc: boolean | string = !!Number(configService.get('API_DOC_ENABLED')); + if (apiDoc) { + apiDoc = setupSwagger(app); + } + + const port = configService.get('PORT'); + console.log(port); + await app.listen(port); + + if (apiDoc) { + // eslint-disable-next-line no-console + console.log(`App started. Swagger listening at http://localhost:${port}/${apiDoc}`); + } + + return port; + } catch (e) { + // eslint-disable-next-line no-console + console.error(`Failed to initialize: message ${e.message}`); + process.exit(1); + } } bootstrap(); diff --git a/src/swagger-setup.ts b/src/swagger-setup.ts new file mode 100644 index 0000000000000000000000000000000000000000..f9160f31f338fc5cde187ee69264be626add2ffb --- /dev/null +++ b/src/swagger-setup.ts @@ -0,0 +1,22 @@ +import { INestApplication } from '@nestjs/common'; +import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; +import { ConfigService } from '@nestjs/config'; + +export const setupSwagger = (app: INestApplication): string => { + const configService = app.get(ConfigService); + + const serviceName = configService.get('SERVICE_NAME'); + const path = `${serviceName}/${configService.get('API_DOC_PATH')}`; + + const options = new DocumentBuilder() + .addBearerAuth() + .setTitle(`${configService.get('NODE_ENV')}/${serviceName}`) // Replace with actual document title here i.e. 'TTL-LIFO API' + .setDescription('api') + .setVersion('0.1') + .addTag(serviceName) // Replace with actual tag here i.e. 'ttl-lifo' + .build(); + const document = SwaggerModule.createDocument(app, options); + SwaggerModule.setup(path, app, document); // Replace with actual swagger module path here i.e. 'ttl-lifo/api' + + return path; +}; \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index bf10a2398e82aadd880869203ee292c822156804..ec873e96952edba27519a539a10dc9d616aba2fc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,15 +1,28 @@ { "compilerOptions": { "module": "commonjs", - "declaration": true, + "declaration": false, + "noImplicitAny": false, "removeComments": true, + "noLib": false, + "allowSyntheticDefaultImports": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, - "allowSyntheticDefaultImports": true, - "target": "es2017", + "resolveJsonModule": true, + "esModuleInterop": true, + "skipLibCheck": true, + "target": "es6", "sourceMap": true, + "allowJs": true, "outDir": "./dist", - "baseUrl": "./", - "incremental": true - } + "baseUrl": "./src" + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "dist", + "**/*.spec.ts" + ] }