From 1752130d76cbbdba297ae87743c343b7d85f4320 Mon Sep 17 00:00:00 2001 From: DQGiac Date: Fri, 15 May 2026 13:23:38 +0700 Subject: [PATCH 01/26] Register for user and lawyer --- backend/package-lock.json | 1654 ++--------------- backend/package.json | 4 +- backend/src/core/api/auth/auth.controller.js | 11 + backend/src/core/api/auth/auth.resolver.js | 18 +- backend/src/core/modules/auth/dto/index.js | 2 + .../modules/auth/dto/register-lawyer.dto.js | 26 + .../modules/auth/dto/register-user.dto.js | 17 + .../core/modules/auth/interceptor/index.js | 2 + .../register-lawyer.interceptor.js | 17 + .../interceptor/register-user.interceptor.js | 12 + .../core/modules/auth/service/auth.service.js | 81 +- backend/yarn.lock | 461 +---- 12 files changed, 341 insertions(+), 1964 deletions(-) create mode 100644 backend/src/core/modules/auth/dto/register-lawyer.dto.js create mode 100644 backend/src/core/modules/auth/dto/register-user.dto.js create mode 100644 backend/src/core/modules/auth/interceptor/register-lawyer.interceptor.js create mode 100644 backend/src/core/modules/auth/interceptor/register-user.interceptor.js diff --git a/backend/package-lock.json b/backend/package-lock.json index c265c51..c35574d 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -58,7 +58,7 @@ "faker": "^5.5.3", "husky": "^8.0.3", "lint-staged": "^15.0.2", - "nodemon": "^2.0.7", + "nodemon": "^2.0.22", "prisma": "^7.8.0" } }, @@ -117,6 +117,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.12.tgz", "integrity": "sha512-dK5PtG1uiN2ikk++5OzSYsitZKny4wOCD0nrO4TqnW4BVBTQ2NGS3NgilvT/TEyxTST7LNyWV/T4tXDoD3fOgg==", "dev": true, + "peer": true, "dependencies": { "@babel/code-frame": "^7.16.7", "@babel/generator": "^7.16.8", @@ -2608,7 +2609,8 @@ "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.4.1.tgz", "integrity": "sha512-mZ9NzzUSYPOCnxHH1oAHPRzoMFJHY472raDKwXl/+6oPbpdJ7g8LsCN4FSaIIfkiCKHhb3iF/Zqo3NYxaIhU7Q==", "devOptional": true, - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/@electric-sql/pglite-socket": { "version": "0.1.1", @@ -3292,15 +3294,6 @@ "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" }, - "node_modules/@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", @@ -3308,18 +3301,6 @@ "devOptional": true, "license": "MIT" }, - "node_modules/@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "dev": true, - "dependencies": { - "defer-to-connect": "^1.0.1" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -3370,6 +3351,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.19.0" } @@ -3423,6 +3405,7 @@ "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3473,15 +3456,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "dev": true, - "dependencies": { - "string-width": "^4.1.0" - } - }, "node_modules/ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -3934,98 +3908,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, - "node_modules/boxen": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", - "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", - "dev": true, - "dependencies": { - "ansi-align": "^3.0.0", - "camelcase": "^6.2.0", - "chalk": "^4.1.0", - "cli-boxes": "^2.2.1", - "string-width": "^4.2.2", - "type-fest": "^0.20.2", - "widest-line": "^3.1.0", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/boxen/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/boxen/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/boxen/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/boxen/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/boxen/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/boxen/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -4172,48 +4054,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", - "dev": true, - "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cacheable-request/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cacheable-request/node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -4248,18 +4088,6 @@ "node": ">=6" } }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/camelcase-keys": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", @@ -4369,24 +4197,6 @@ "node": ">=10" } }, - "node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, - "node_modules/cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/cli-cursor": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", @@ -4536,15 +4346,6 @@ "node": ">=6" } }, - "node_modules/clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= sha512-yjLXh88P599UOyPTFX0POsd7WxnbsVsGohcwzHOLspIhhpalPw1BcqED8NblyZLKcGrL8dTgMlcaZxV2jAD41Q==", - "dev": true, - "dependencies": { - "mimic-response": "^1.0.0" - } - }, "node_modules/cloudinary": { "version": "1.28.1", "resolved": "https://registry.npmjs.org/cloudinary/-/cloudinary-1.28.1.tgz", @@ -4728,38 +4529,6 @@ "devOptional": true, "license": "MIT" }, - "node_modules/configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", - "dev": true, - "dependencies": { - "dot-prop": "^5.2.0", - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "unique-string": "^2.0.0", - "write-file-atomic": "^3.0.0", - "xdg-basedir": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/configstore/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/confusing-browser-globals": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", @@ -5059,22 +4828,12 @@ "node": ">= 8" } }, - "node_modules/crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "devOptional": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/dargs": { "version": "7.0.0", @@ -5144,27 +4903,6 @@ "node": ">=0.10.0" } }, - "node_modules/decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==", - "dev": true, - "dependencies": { - "mimic-response": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -5180,12 +4918,6 @@ "node": ">=16.0.0" } }, - "node_modules/defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", - "dev": true - }, "node_modules/define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -5340,12 +5072,6 @@ "node": ">= 0.4" } }, - "node_modules/duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= sha512-CEj8FwwNA4cVH2uFCoHUrmojhYh1vmCdOaneKJXwkeY1i9jnlslVo9dx+hQ5Hl9GnH/Bwy/IjxAyOePyPKYnzA==", - "dev": true - }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -5410,15 +5136,6 @@ "node": ">= 0.8" } }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "dependencies": { - "once": "^1.4.0" - } - }, "node_modules/enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", @@ -5563,15 +5280,6 @@ "node": ">=6" } }, - "node_modules/escape-goat": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", - "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -5671,6 +5379,7 @@ "version": "7.32.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "peer": true, "dependencies": { "@babel/code-frame": "7.12.11", "@eslint/eslintrc": "^0.4.3", @@ -5798,6 +5507,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz", "integrity": "sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA==", "dev": true, + "peer": true, "dependencies": { "array-includes": "^3.1.4", "array.prototype.flat": "^1.2.5", @@ -6742,18 +6452,6 @@ "node": ">= 0.4" } }, - "node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/get-symbol-description": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", @@ -6898,21 +6596,6 @@ "node": ">= 6" } }, - "node_modules/global-dirs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", - "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", - "dev": true, - "dependencies": { - "ini": "2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -6934,28 +6617,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "dev": true, - "dependencies": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" - }, - "engines": { - "node": ">=8.6" - } - }, "node_modules/graceful-fs": { "version": "4.2.9", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", @@ -7047,15 +6708,6 @@ "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", "license": "ISC" }, - "node_modules/has-yarn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", - "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -7086,6 +6738,7 @@ "integrity": "sha512-jN0ZewiNAWSe5khM3EyCmBb250+b40wWbwNILNfEvq84VREWwOIkuUsFONk/3i3nqkz7Oe1PcpM2mwQEK2L9Kg==", "devOptional": true, "license": "MIT", + "peer": true, "engines": { "node": ">=16.9.0" } @@ -7120,12 +6773,6 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "node_modules/http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", - "dev": true - }, "node_modules/http-errors": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", @@ -7252,15 +6899,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -7292,15 +6930,6 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "node_modules/ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/internal-slot": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", @@ -7386,18 +7015,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "dependencies": { - "ci-info": "^2.0.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, "node_modules/is-core-module": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", @@ -7452,22 +7069,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", - "dev": true, - "dependencies": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-negative-zero": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", @@ -7480,18 +7081,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-npm": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz", - "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -7525,15 +7114,6 @@ "node": ">=8" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", @@ -7640,12 +7220,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true - }, "node_modules/is-weakref": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", @@ -7658,12 +7232,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-yarn-global": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", - "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", - "dev": true - }, "node_modules/isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", @@ -7734,12 +7302,6 @@ "node": ">=4" } }, - "node_modules/json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==", - "dev": true - }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -7853,15 +7415,6 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.0" - } - }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -7876,18 +7429,6 @@ "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" }, - "node_modules/latest-version": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", - "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", - "dev": true, - "dependencies": { - "package-json": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -8116,7 +7657,8 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "peer": true }, "node_modules/lodash.camelcase": { "version": "4.3.0", @@ -8362,15 +7904,6 @@ "devOptional": true, "license": "Apache-2.0" }, - "node_modules/lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -8591,15 +8124,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -8610,9 +8134,10 @@ } }, "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -8842,22 +8367,22 @@ "dev": true }, "node_modules/nodemon": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.15.tgz", - "integrity": "sha512-gdHMNx47Gw7b3kWxJV64NI+Q5nfl0y5DgDbiVtShiwa7Z0IZ07Ll4RLFo6AjrhzMtoEZn5PDE3/c2AbVsiCkpA==", + "version": "2.0.22", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", + "integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==", "dev": true, - "hasInstallScript": true, + "license": "MIT", "dependencies": { "chokidar": "^3.5.2", "debug": "^3.2.7", "ignore-by-default": "^1.0.1", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "pstree.remy": "^1.1.8", "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", "supports-color": "^5.5.0", "touch": "^3.1.0", - "undefsafe": "^2.0.5", - "update-notifier": "^5.1.0" + "undefsafe": "^2.0.5" }, "bin": { "nodemon": "bin/nodemon.js" @@ -8960,15 +8485,6 @@ "node": ">=0.10.0" } }, - "node_modules/normalize-url": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/npm-run-path": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", @@ -9166,15 +8682,6 @@ "node": ">= 0.8.0" } }, - "node_modules/p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", @@ -9242,21 +8749,6 @@ "node": ">= 8" } }, - "node_modules/package-json": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", - "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", - "dev": true, - "dependencies": { - "got": "^9.6.0", - "registry-auth-token": "^4.0.0", - "registry-url": "^5.0.0", - "semver": "^6.2.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -9711,19 +9203,11 @@ "node": ">= 0.8.0" } }, - "node_modules/prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/prettier": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", + "peer": true, "bin": { "prettier": "bin-prettier.js" }, @@ -9749,6 +9233,7 @@ "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@prisma/config": "7.8.0", "@prisma/dev": "0.24.3", @@ -9844,16 +9329,6 @@ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", "dev": true }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "node_modules/punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -9862,18 +9337,6 @@ "node": ">=6" } }, - "node_modules/pupa": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", - "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", - "dev": true, - "dependencies": { - "escape-goat": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/pure-rand": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", @@ -9942,36 +9405,6 @@ "node": ">= 0.8" } }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo= sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/rc9": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/rc9/-/rc9-3.0.1.tgz", @@ -10252,30 +9685,6 @@ "node": ">=4" } }, - "node_modules/registry-auth-token": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", - "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", - "dev": true, - "dependencies": { - "rc": "^1.2.8" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/registry-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", - "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", - "dev": true, - "dependencies": { - "rc": "^1.2.8" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/regjsgen": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", @@ -10391,15 +9800,6 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, - "node_modules/responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==", - "dev": true, - "dependencies": { - "lowercase-keys": "^1.0.0" - } - }, "node_modules/restore-cursor": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", @@ -10490,8 +9890,7 @@ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "devOptional": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/semver": { "version": "6.3.0", @@ -10502,18 +9901,6 @@ "semver": "bin/semver.js" } }, - "node_modules/semver-diff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", - "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", - "dev": true, - "dependencies": { - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/send": { "version": "0.17.2", "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", @@ -10644,6 +10031,29 @@ "is-arrayish": "^0.3.1" } }, + "node_modules/simple-update-notifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", + "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "~7.0.0" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/slash": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", @@ -11158,15 +10568,6 @@ "node": ">=4" } }, - "node_modules/to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -11239,6 +10640,7 @@ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", "dev": true, + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -11358,15 +10760,6 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, "node_modules/typescript": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", @@ -11449,18 +10842,6 @@ "node": ">=4" } }, - "node_modules/unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "dev": true, - "dependencies": { - "crypto-random-string": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -11478,188 +10859,45 @@ "node": ">= 0.8" } }, - "node_modules/update-notifier": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz", - "integrity": "sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==", - "dev": true, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dependencies": { - "boxen": "^5.0.0", - "chalk": "^4.1.0", - "configstore": "^5.0.1", - "has-yarn": "^2.1.0", - "import-lazy": "^2.1.0", - "is-ci": "^2.0.0", - "is-installed-globally": "^0.4.0", - "is-npm": "^5.0.0", - "is-yarn-global": "^0.3.0", - "latest-version": "^5.1.0", - "pupa": "^2.1.1", - "semver": "^7.3.4", - "semver-diff": "^3.1.1", - "xdg-basedir": "^4.0.0" - }, + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/yeoman/update-notifier?sponsor=1" + "node": ">= 0.4.0" } }, - "node_modules/update-notifier/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==" + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/v8flags": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", + "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/update-notifier/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/update-notifier/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/update-notifier/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/update-notifier/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/update-notifier/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/update-notifier/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/update-notifier/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/update-notifier/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==", - "dev": true, - "dependencies": { - "prepend-http": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==" - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true - }, - "node_modules/v8flags": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", - "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", - "dev": true, - "dependencies": { - "homedir-polyfill": "^1.0.1" + "homedir-polyfill": "^1.0.1" }, "engines": { "node": ">= 0.10" @@ -11766,18 +11004,6 @@ "string-width": "^1.0.2 || 2 || 3 || 4" } }, - "node_modules/widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", - "dev": true, - "dependencies": { - "string-width": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/winston": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/winston/-/winston-3.4.0.tgz", @@ -11953,27 +11179,6 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, - "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "node_modules/xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/xregexp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz", @@ -12122,6 +11327,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.12.tgz", "integrity": "sha512-dK5PtG1uiN2ikk++5OzSYsitZKny4wOCD0nrO4TqnW4BVBTQ2NGS3NgilvT/TEyxTST7LNyWV/T4tXDoD3fOgg==", "dev": true, + "peer": true, "requires": { "@babel/code-frame": "^7.16.7", "@babel/generator": "^7.16.8", @@ -13884,7 +13090,8 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.4.1.tgz", "integrity": "sha512-mZ9NzzUSYPOCnxHH1oAHPRzoMFJHY472raDKwXl/+6oPbpdJ7g8LsCN4FSaIIfkiCKHhb3iF/Zqo3NYxaIhU7Q==", - "devOptional": true + "devOptional": true, + "peer": true }, "@electric-sql/pglite-socket": { "version": "0.1.1", @@ -14372,27 +13579,12 @@ "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" }, - "@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", - "dev": true - }, "@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "devOptional": true }, - "@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "dev": true, - "requires": { - "defer-to-connect": "^1.0.1" - } - }, "@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -14439,6 +13631,7 @@ "version": "25.6.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", + "peer": true, "requires": { "undici-types": "~7.19.0" } @@ -14486,7 +13679,8 @@ "acorn": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "peer": true }, "acorn-jsx": { "version": "5.3.2", @@ -14519,15 +13713,6 @@ "uri-js": "^4.2.2" } }, - "ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "dev": true, - "requires": { - "string-width": "^4.1.0" - } - }, "ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -14876,73 +14061,6 @@ } } }, - "boxen": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", - "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", - "dev": true, - "requires": { - "ansi-align": "^3.0.0", - "camelcase": "^6.2.0", - "chalk": "^4.1.0", - "cli-boxes": "^2.2.1", - "string-width": "^4.2.2", - "type-fest": "^0.20.2", - "widest-line": "^3.1.0", - "wrap-ansi": "^7.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -15041,38 +14159,6 @@ } } }, - "cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", - "dev": true, - "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" - }, - "dependencies": { - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true - } - } - }, "call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -15097,12 +14183,6 @@ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" }, - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true - }, "camelcase-keys": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", @@ -15168,18 +14248,6 @@ "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" }, - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, - "cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", - "dev": true - }, "cli-cursor": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", @@ -15277,15 +14345,6 @@ "shallow-clone": "^3.0.0" } }, - "clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= sha512-yjLXh88P599UOyPTFX0POsd7WxnbsVsGohcwzHOLspIhhpalPw1BcqED8NblyZLKcGrL8dTgMlcaZxV2jAD41Q==", - "dev": true, - "requires": { - "mimic-response": "^1.0.0" - } - }, "cloudinary": { "version": "1.28.1", "resolved": "https://registry.npmjs.org/cloudinary/-/cloudinary-1.28.1.tgz", @@ -15442,31 +14501,6 @@ "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", "devOptional": true }, - "configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", - "dev": true, - "requires": { - "dot-prop": "^5.2.0", - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "unique-string": "^2.0.0", - "write-file-atomic": "^3.0.0", - "xdg-basedir": "^4.0.0" - }, - "dependencies": { - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } - } - } - }, "confusing-browser-globals": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", @@ -15676,18 +14710,11 @@ "which": "^2.0.1" } }, - "crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "dev": true - }, "csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "devOptional": true, - "peer": true + "devOptional": true }, "dargs": { "version": "7.0.0", @@ -15733,21 +14760,6 @@ } } }, - "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==", - "dev": true, - "requires": { - "mimic-response": "^1.0.0" - } - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true - }, "deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -15759,12 +14771,6 @@ "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", "devOptional": true }, - "defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", - "dev": true - }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -15876,12 +14882,6 @@ "gopd": "^1.2.0" } }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= sha512-CEj8FwwNA4cVH2uFCoHUrmojhYh1vmCdOaneKJXwkeY1i9jnlslVo9dx+hQ5Hl9GnH/Bwy/IjxAyOePyPKYnzA==", - "dev": true - }, "eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -15938,15 +14938,6 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, "enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", @@ -16052,12 +15043,6 @@ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "dev": true }, - "escape-goat": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", - "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", - "dev": true - }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -16132,6 +15117,7 @@ "version": "7.32.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "peer": true, "requires": { "@babel/code-frame": "7.12.11", "@eslint/eslintrc": "^0.4.3", @@ -16331,6 +15317,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz", "integrity": "sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA==", "dev": true, + "peer": true, "requires": { "array-includes": "^3.1.4", "array.prototype.flat": "^1.2.5", @@ -16910,15 +15897,6 @@ "es-object-atoms": "^1.0.0" } }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, "get-symbol-description": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", @@ -17020,15 +15998,6 @@ "is-glob": "^4.0.1" } }, - "global-dirs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", - "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", - "dev": true, - "requires": { - "ini": "2.0.0" - } - }, "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -17040,25 +16009,6 @@ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" }, - "got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "dev": true, - "requires": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" - } - }, "graceful-fs": { "version": "4.2.9", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", @@ -17121,12 +16071,6 @@ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" }, - "has-yarn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", - "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", - "dev": true - }, "hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -17148,7 +16092,8 @@ "version": "4.12.16", "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.16.tgz", "integrity": "sha512-jN0ZewiNAWSe5khM3EyCmBb250+b40wWbwNILNfEvq84VREWwOIkuUsFONk/3i3nqkz7Oe1PcpM2mwQEK2L9Kg==", - "devOptional": true + "devOptional": true, + "peer": true }, "hosted-git-info": { "version": "4.1.0", @@ -17176,12 +16121,6 @@ } } }, - "http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", - "dev": true - }, "http-errors": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", @@ -17270,12 +16209,6 @@ "resolve-from": "^4.0.0" } }, - "import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A==", - "dev": true - }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -17301,12 +16234,6 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "dev": true - }, "internal-slot": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", @@ -17368,15 +16295,6 @@ "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", "dev": true }, - "is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "requires": { - "ci-info": "^2.0.0" - } - }, "is-core-module": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", @@ -17413,28 +16331,12 @@ "is-extglob": "^2.1.1" } }, - "is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", - "dev": true, - "requires": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" - } - }, "is-negative-zero": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", "dev": true }, - "is-npm": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz", - "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==", - "dev": true - }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -17456,12 +16358,6 @@ "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", "dev": true }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true - }, "is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", @@ -17531,12 +16427,6 @@ "text-extensions": "^1.0.0" } }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true - }, "is-weakref": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", @@ -17546,12 +16436,6 @@ "call-bind": "^1.0.2" } }, - "is-yarn-global": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", - "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", - "dev": true - }, "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", @@ -17606,12 +16490,6 @@ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, - "json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==", - "dev": true - }, "json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -17705,15 +16583,6 @@ "safe-buffer": "^5.0.1" } }, - "keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "dev": true, - "requires": { - "json-buffer": "3.0.0" - } - }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -17725,15 +16594,6 @@ "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" }, - "latest-version": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", - "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", - "dev": true, - "requires": { - "package-json": "^6.3.0" - } - }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -17896,7 +16756,8 @@ "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "peer": true }, "lodash.camelcase": { "version": "4.3.0", @@ -18095,12 +16956,6 @@ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", "devOptional": true }, - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true - }, "lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -18259,12 +17114,6 @@ "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", "dev": true }, - "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true - }, "min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -18272,9 +17121,9 @@ "dev": true }, "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "requires": { "brace-expansion": "^1.1.7" } @@ -18443,21 +17292,21 @@ "dev": true }, "nodemon": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.15.tgz", - "integrity": "sha512-gdHMNx47Gw7b3kWxJV64NI+Q5nfl0y5DgDbiVtShiwa7Z0IZ07Ll4RLFo6AjrhzMtoEZn5PDE3/c2AbVsiCkpA==", + "version": "2.0.22", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", + "integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==", "dev": true, "requires": { "chokidar": "^3.5.2", "debug": "^3.2.7", "ignore-by-default": "^1.0.1", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "pstree.remy": "^1.1.8", "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", "supports-color": "^5.5.0", "touch": "^3.1.0", - "undefsafe": "^2.0.5", - "update-notifier": "^5.1.0" + "undefsafe": "^2.0.5" }, "dependencies": { "debug": { @@ -18529,12 +17378,6 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, - "normalize-url": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", - "dev": true - }, "npm-run-path": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", @@ -18677,12 +17520,6 @@ "word-wrap": "^1.2.3" } }, - "p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", - "dev": true - }, "p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", @@ -18735,18 +17572,6 @@ "netmask": "^2.0.1" } }, - "package-json": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", - "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", - "dev": true, - "requires": { - "got": "^9.6.0", - "registry-auth-token": "^4.0.0", - "registry-url": "^5.0.0", - "semver": "^6.2.0" - } - }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -19068,16 +17893,11 @@ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==" }, - "prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==", - "dev": true - }, "prettier": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", - "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==" + "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", + "peer": true }, "prettier-linter-helpers": { "version": "1.0.0", @@ -19092,6 +17912,7 @@ "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.8.0.tgz", "integrity": "sha512-yfN4yrw7HV9kEJhoy1+jgah0jafEIQsf7uWouSsM8MvJtlubsk+kM7AIBWZ8+GJl74Yj3c+nbYqBkMOxtsZ3Lw==", "devOptional": true, + "peer": true, "requires": { "@prisma/config": "7.8.0", "@prisma/dev": "0.24.3", @@ -19159,30 +17980,11 @@ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", "dev": true }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, - "pupa": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", - "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", - "dev": true, - "requires": { - "escape-goat": "^2.0.0" - } - }, "pure-rand": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", @@ -19221,32 +18023,6 @@ "unpipe": "1.0.0" } }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo= sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "dev": true - } - } - }, "rc9": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/rc9/-/rc9-3.0.1.tgz", @@ -19465,24 +18241,6 @@ "unicode-match-property-value-ecmascript": "^2.0.0" } }, - "registry-auth-token": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", - "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", - "dev": true, - "requires": { - "rc": "^1.2.8" - } - }, - "registry-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", - "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", - "dev": true, - "requires": { - "rc": "^1.2.8" - } - }, "regjsgen": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", @@ -19571,15 +18329,6 @@ } } }, - "responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==", - "dev": true, - "requires": { - "lowercase-keys": "^1.0.0" - } - }, "restore-cursor": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", @@ -19646,8 +18395,7 @@ "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "devOptional": true, - "peer": true + "devOptional": true }, "semver": { "version": "6.3.0", @@ -19655,15 +18403,6 @@ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, - "semver-diff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", - "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", - "dev": true, - "requires": { - "semver": "^6.3.0" - } - }, "send": { "version": "0.17.2", "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", @@ -19779,6 +18518,23 @@ "is-arrayish": "^0.3.1" } }, + "simple-update-notifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", + "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", + "dev": true, + "requires": { + "semver": "~7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true + } + } + }, "slash": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", @@ -20160,12 +18916,6 @@ "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", "dev": true }, - "to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", - "dev": true - }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -20221,6 +18971,7 @@ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", "dev": true, + "peer": true, "requires": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -20301,15 +19052,6 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "requires": { - "is-typedarray": "^1.0.0" - } - }, "typescript": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", @@ -20368,15 +19110,6 @@ "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", "dev": true }, - "unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "dev": true, - "requires": { - "crypto-random-string": "^2.0.0" - } - }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -20388,103 +19121,6 @@ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" }, - "update-notifier": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz", - "integrity": "sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==", - "dev": true, - "requires": { - "boxen": "^5.0.0", - "chalk": "^4.1.0", - "configstore": "^5.0.1", - "has-yarn": "^2.1.0", - "import-lazy": "^2.1.0", - "is-ci": "^2.0.0", - "is-installed-globally": "^0.4.0", - "is-npm": "^5.0.0", - "is-yarn-global": "^0.3.0", - "latest-version": "^5.1.0", - "pupa": "^2.1.1", - "semver": "^7.3.4", - "semver-diff": "^3.1.1", - "xdg-basedir": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -20493,15 +19129,6 @@ "punycode": "^2.1.0" } }, - "url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==", - "dev": true, - "requires": { - "prepend-http": "^2.0.0" - } - }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -20603,15 +19230,6 @@ "string-width": "^1.0.2 || 2 || 3 || 4" } }, - "widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", - "dev": true, - "requires": { - "string-width": "^4.0.0" - } - }, "winston": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/winston/-/winston-3.4.0.tgz", @@ -20735,24 +19353,6 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, - "write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", - "dev": true - }, "xregexp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz", diff --git a/backend/package.json b/backend/package.json index 33f51df..db0745b 100644 --- a/backend/package.json +++ b/backend/package.json @@ -12,7 +12,7 @@ "prepare": "test -d node_modules/husky && husky install || echo \"husky is not installed\"", "lint": "eslint --fix --ext .js .", "lint:staged": "lint-staged", - "dev": "nodemon --exec npx babel-node src/core/bin/www.js", + "dev": "npx nodemon --exec npx babel-node src/core/bin/www.js", "start": "npm run build && node ./dist/core/bin/www.js", "clean": "rm -rf dist && mkdir dist", "build:babel": "babel ./src --out-dir dist --copy-files", @@ -71,7 +71,7 @@ "faker": "^5.5.3", "husky": "^8.0.3", "lint-staged": "^15.0.2", - "nodemon": "^2.0.7", + "nodemon": "^2.0.22", "prisma": "^7.8.0" } } diff --git a/backend/src/core/api/auth/auth.controller.js b/backend/src/core/api/auth/auth.controller.js index 1671d8a..4a94cbf 100644 --- a/backend/src/core/api/auth/auth.controller.js +++ b/backend/src/core/api/auth/auth.controller.js @@ -1,4 +1,5 @@ import { AuthService } from '../../modules/auth/service/auth.service'; +import { RegisterUserDto, RegisterLawyerDto } from '../../modules/auth'; import { LoginDto } from '../../modules/auth'; import { ValidHttpResponse } from '../../../packages/handler/response/validHttp.response'; @@ -7,6 +8,16 @@ class Controller { this.service = AuthService; } + registerUser = async req => { + const data = await this.service.register(RegisterUserDto(req.body)); + return ValidHttpResponse.toOkResponse(data); + } + + registerLawyer = async req => { + const data = await this.service.register(RegisterLawyerDto(req.body)); + return ValidHttpResponse.toOkResponse(data); + } + login = async req => { const data = await this.service.login(LoginDto(req.body)); return ValidHttpResponse.toOkResponse(data); diff --git a/backend/src/core/api/auth/auth.resolver.js b/backend/src/core/api/auth/auth.resolver.js index 123aa1f..96c2e41 100644 --- a/backend/src/core/api/auth/auth.resolver.js +++ b/backend/src/core/api/auth/auth.resolver.js @@ -1,6 +1,7 @@ -import { LoginInterceptor } from 'core/modules/auth'; +import { LoginInterceptor, RegisterUserDto, RegisterLawyerDto } from 'core/modules/auth'; import { Module } from 'packages/handler/Module'; import { AuthController } from './auth.controller'; +import { RegisterLawyerInterceptor, RegisterUserInterceptor } from 'core/modules/auth/'; export const AuthResolver = Module.builder() .addPrefix({ @@ -10,10 +11,17 @@ export const AuthResolver = Module.builder() }) .register([ { - route: '/login', + route: '/register_user', method: 'post', - interceptors: [LoginInterceptor], - body: 'LoginDto', - controller: AuthController.login, + interceptors: [RegisterUserInterceptor], + body: 'RegisterUserDto', + controller: AuthController.registerUser, }, + { + route: '/register_lawyer', + method: 'post', + interceptors: [RegisterLawyerInterceptor], + body: 'RegisterLawyerDto', + controller: AuthController.registerLawyer, + } ]); diff --git a/backend/src/core/modules/auth/dto/index.js b/backend/src/core/modules/auth/dto/index.js index a0517c4..fe239d5 100644 --- a/backend/src/core/modules/auth/dto/index.js +++ b/backend/src/core/modules/auth/dto/index.js @@ -1,2 +1,4 @@ export * from './login.dto'; +export * from './register-user.dto'; +export * from './register-lawyer.dto'; export * from './jwt-sign.dto'; diff --git a/backend/src/core/modules/auth/dto/register-lawyer.dto.js b/backend/src/core/modules/auth/dto/register-lawyer.dto.js new file mode 100644 index 0000000..8e41580 --- /dev/null +++ b/backend/src/core/modules/auth/dto/register-lawyer.dto.js @@ -0,0 +1,26 @@ +import { ApiDocument } from "core/config/swagger.config"; +import { SwaggerDocument } from "packages/swagger"; + +ApiDocument.addModel('RegisterLawyerDto', { + role: SwaggerDocument.ApiProperty({type: 'string'}), + email: SwaggerDocument.ApiProperty({type: 'string'}), + password: SwaggerDocument.ApiProperty({type: 'string'}), + confirmPassword: SwaggerDocument.ApiProperty({type: 'string'}), + licenseNumber: SwaggerDocument.ApiProperty({type: 'string'}), + licenseIssuer: SwaggerDocument.ApiProperty({type: 'string'}), + licenseIssueDate: SwaggerDocument.ApiProperty({type: 'string'}), + licenseFile: SwaggerDocument.ApiProperty({type: 'string'}), + referralCode: SwaggerDocument.ApiProperty({type: 'string'}), +}); + +export const RegisterLawyerDto = body => ({ + role: body.role, + email: body.email, + password: body.password, + confirmPassword: body.confirmPassword, + licenseNumber: body.licenseNumber, + licenseIssuer: body.licenseIssuer, + licenseIssueDate: body.licenseIssueDate, + licenseFile: body.licenseFile, + referralCode: body.referralCode || null, +}); \ No newline at end of file diff --git a/backend/src/core/modules/auth/dto/register-user.dto.js b/backend/src/core/modules/auth/dto/register-user.dto.js new file mode 100644 index 0000000..32695a2 --- /dev/null +++ b/backend/src/core/modules/auth/dto/register-user.dto.js @@ -0,0 +1,17 @@ +import { ApiDocument } from 'core/config/swagger.config'; +import { SwaggerDocument } from 'packages/swagger'; + +ApiDocument.addModel('RegisterUserDto', { + role: SwaggerDocument.ApiProperty({ type: 'string' }), + email: SwaggerDocument.ApiProperty({ type: 'string' }), + password: SwaggerDocument.ApiProperty({ type: 'string' }), + confirmPassword: SwaggerDocument.ApiProperty({ type: 'string' }), +}); + +export const RegisterUserDto = body => ({ + role: body.role, + email: body.email, + password: body.password, + confirmPassword: body.confirmPassword, + referralCode: null +}); \ No newline at end of file diff --git a/backend/src/core/modules/auth/interceptor/index.js b/backend/src/core/modules/auth/interceptor/index.js index a330565..7654dec 100644 --- a/backend/src/core/modules/auth/interceptor/index.js +++ b/backend/src/core/modules/auth/interceptor/index.js @@ -1 +1,3 @@ export * from './login.interceptor'; +export * from './register-lawyer.interceptor'; +export * from './register-user.interceptor'; diff --git a/backend/src/core/modules/auth/interceptor/register-lawyer.interceptor.js b/backend/src/core/modules/auth/interceptor/register-lawyer.interceptor.js new file mode 100644 index 0000000..c28ba1a --- /dev/null +++ b/backend/src/core/modules/auth/interceptor/register-lawyer.interceptor.js @@ -0,0 +1,17 @@ +import { DefaultValidatorInterceptor } from 'core/infrastructure/interceptor'; +import { JoiUtils } from 'core/utils'; +import Joi from 'joi'; + +export const RegisterLawyerInterceptor = new DefaultValidatorInterceptor( + Joi.object({ + role: JoiUtils.requiredString(), + email: JoiUtils.email().required(), + password: JoiUtils.password().required(), + confirmPassword: JoiUtils.password().required(), + licenseNumber: JoiUtils.requiredString(), + licenseIssuer: JoiUtils.requiredString(), + licenseIssueDate: JoiUtils.requiredString().isoDate(), // e khong ro cai format date + licenseFile: JoiUtils.requiredString(), + referralCode: JoiUtils.optionalString().allow(''), + }) +); \ No newline at end of file diff --git a/backend/src/core/modules/auth/interceptor/register-user.interceptor.js b/backend/src/core/modules/auth/interceptor/register-user.interceptor.js new file mode 100644 index 0000000..b52dcf6 --- /dev/null +++ b/backend/src/core/modules/auth/interceptor/register-user.interceptor.js @@ -0,0 +1,12 @@ +import { DefaultValidatorInterceptor } from 'core/infrastructure/interceptor'; +import { JoiUtils } from 'core/utils'; +import Joi from 'joi'; + +export const RegisterUserInterceptor = new DefaultValidatorInterceptor( + Joi.object({ + role: JoiUtils.requiredString(), + email: JoiUtils.email().required(), + password: JoiUtils.password().required(), + confirmPassword: JoiUtils.password().required(), + }) +); \ No newline at end of file diff --git a/backend/src/core/modules/auth/service/auth.service.js b/backend/src/core/modules/auth/service/auth.service.js index 365063e..880be90 100644 --- a/backend/src/core/modules/auth/service/auth.service.js +++ b/backend/src/core/modules/auth/service/auth.service.js @@ -1,3 +1,4 @@ +import connection from 'core/database'; import { pick } from 'lodash'; import { JwtPayload } from 'core/modules/auth/dto/jwt-sign.dto'; import { UserDataService } from 'core/modules/user/services/userData.service'; @@ -5,7 +6,7 @@ import { joinUserRoles } from 'core/utils/userFilter'; import { BcryptService } from './bcrypt.service'; import { JwtService } from './jwt.service'; import { UserRepository } from '../../user/user.repository'; -import { UnAuthorizedException } from '../../../../packages/httpException'; +import { UnAuthorizedException, DuplicateException, BadRequestException } from '../../../../packages/httpException'; class Service { constructor() { @@ -14,6 +15,84 @@ class Service { this.bcryptService = BcryptService; } + // async registerLawyer_addon(trx, registerLawyerDto, lawyer_id) { + // await trx.lawyer_certificates.create({ + // data: { + // lawyer_id: lawyer_id, + // file_url: registerLawyerDto.licenseFile, + // issued_by: registerLawyerDto.licenseIssuer, + // issue_date: registerLawyerDto.licenseIssueDate, + // certificate_name: "placeholder" // cai nay o day de giu cho (@c quynh) + // }, + // select: { id: true } + // }); + // await trx.lawyer_details.create({ + // data: { + // user_id: lawyer_id, + // email: registerLawyerDto.email, + // password_hash: registerLawyerDto.password, + // full_name: registerLawyerDto.fullName, + // license_number: registerLawyerDto.licenseNumber, + // bar_association: registerLawyerDto.licenseIssuer, // khong biet co dung khong (@c quynh) + // is_verified: true, // (@c quynh) + // status: "AVAILABLE" // (@c quynh) + // }, + // select: { id: true } + // }); + // }; + + async register(registerDto) { + const existingEmail = await this.userRepository.findByEmail(registerDto.email); + if (existingEmail) { + throw new DuplicateException("Email already in use"); + } + + if (registerDto.password !== registerDto.confirmPassword) { + throw new BadRequestException("Passwords do not match " + registerDto.password + ' ' + registerDto.confirmPassword); + } + + let existingRole = await connection.roles.findFirst({ + where: {name: registerDto.role} + }); + if (!existingRole) { + throw new BadRequestException("Role does not exist"); + } + + registerDto.password = await this.bcryptService.hash(registerDto.password); + + let role_id = existingRole.id; + const result = await connection.$transaction(async (trx) => { + const insertedUser = await trx.users.create({ + data: { + role_id: role_id, + email: registerDto.email, + password_hash: registerDto.password, + referral_code: registerDto.referralCode, + full_name: "placeholder", // cai nay o day de giu cho (@c quynh) + }, + select: { id: true } + }); + + // if (registerDto.role === "LAWYER") { + // await this.registerLawyer_addon(trx, registerDto, insertedUser.id); + // } + + return insertedUser; + }); + + // them phan access token vs refresh token sau. + + return { + id: result.id, + email: registerDto.email, + password_hash: registerDto.password, + role_id: role_id, + referral_code: registerDto.referralCode, + // access_token: accessToken, + // refresh_token: accessTokenString, + }; + } + async login(loginDto) { const user = await this.userRepository.findByEmail(loginDto.email); diff --git a/backend/yarn.lock b/backend/yarn.lock index c9c4d4a..1ada5eb 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -1496,23 +1496,11 @@ resolved "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz" integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== -"@sindresorhus/is@^0.14.0": - version "0.14.0" - resolved "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz" - integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== - "@standard-schema/spec@^1.0.0": version "1.1.0" resolved "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz" integrity sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w== -"@szmarczak/http-timer@^1.1.2": - version "1.1.2" - resolved "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz" - integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== - dependencies: - defer-to-connect "^1.0.1" - "@tootallnate/once@1": version "1.1.2" resolved "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz" @@ -1661,13 +1649,6 @@ ajv@^8.12.0: json-schema-traverse "^1.0.0" require-from-string "^2.0.2" -ansi-align@^3.0.0: - version "3.0.1" - resolved "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz" - integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w== - dependencies: - string-width "^4.1.0" - ansi-colors@^4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz" @@ -1938,20 +1919,6 @@ body-parser@1.19.1: raw-body "2.4.2" type-is "~1.6.18" -boxen@^5.0.0: - version "5.1.2" - resolved "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz" - integrity sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ== - dependencies: - ansi-align "^3.0.0" - camelcase "^6.2.0" - chalk "^4.1.0" - cli-boxes "^2.2.1" - string-width "^4.2.2" - type-fest "^0.20.2" - widest-line "^3.1.0" - wrap-ansi "^7.0.0" - brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" @@ -2019,19 +1986,6 @@ c12@3.3.4: pkg-types "^2.3.0" rc9 "^3.0.1" -cacheable-request@^6.0.0: - version "6.1.0" - resolved "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz" - integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== - dependencies: - clone-response "^1.0.2" - get-stream "^5.1.0" - http-cache-semantics "^4.0.0" - keyv "^3.0.0" - lowercase-keys "^2.0.0" - normalize-url "^4.1.0" - responselike "^1.0.2" - call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz" @@ -2067,11 +2021,6 @@ camelcase@^5.3.1: resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -camelcase@^6.2.0: - version "6.3.0" - resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz" - integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== - caniuse-lite@^1.0.30001286: version "1.0.30001792" resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001792.tgz" @@ -2141,16 +2090,6 @@ chownr@^2.0.0: resolved "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz" integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== -ci-info@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz" - integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== - -cli-boxes@^2.2.1: - version "2.2.1" - resolved "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz" - integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== - cli-cursor@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz" @@ -2184,13 +2123,6 @@ clone-deep@^4.0.1: kind-of "^6.0.2" shallow-clone "^3.0.0" -clone-response@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz" - integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= sha512-yjLXh88P599UOyPTFX0POsd7WxnbsVsGohcwzHOLspIhhpalPw1BcqED8NblyZLKcGrL8dTgMlcaZxV2jAD41Q== - dependencies: - mimic-response "^1.0.0" - cloudinary-core@^2.10.2: version "2.12.3" resolved "https://registry.npmjs.org/cloudinary-core/-/cloudinary-core-2.12.3.tgz" @@ -2321,18 +2253,6 @@ confbox@^0.2.4: resolved "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz" integrity sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ== -configstore@^5.0.1: - version "5.0.1" - resolved "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz" - integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== - dependencies: - dot-prop "^5.2.0" - graceful-fs "^4.1.2" - make-dir "^3.0.0" - unique-string "^2.0.0" - write-file-atomic "^3.0.0" - xdg-basedir "^4.0.0" - 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" @@ -2463,11 +2383,6 @@ cross-spawn@^7.0.2, cross-spawn@^7.0.3, cross-spawn@^7.0.6: shebang-command "^2.0.0" which "^2.0.1" -crypto-random-string@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz" - integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== - csstype@^3.2.2: version "3.2.3" resolved "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz" @@ -2538,18 +2453,6 @@ decamelize@^1.1.0: resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz" integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== -decompress-response@^3.3.0: - version "3.3.0" - resolved "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz" - integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA== - dependencies: - mimic-response "^1.0.0" - -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.4" resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" @@ -2560,11 +2463,6 @@ deepmerge-ts@7.1.5: resolved "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz" integrity sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw== -defer-to-connect@^1.0.1: - version "1.1.3" - resolved "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz" - integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== - define-properties@^1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz" @@ -2649,7 +2547,7 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -dot-prop@^5.1.0, dot-prop@^5.2.0: +dot-prop@^5.1.0: version "5.3.0" resolved "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz" integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== @@ -2675,11 +2573,6 @@ dunder-proto@^1.0.1: es-errors "^1.3.0" gopd "^1.2.0" -duplexer3@^0.1.4: - version "0.1.4" - resolved "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz" - integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= sha512-CEj8FwwNA4cVH2uFCoHUrmojhYh1vmCdOaneKJXwkeY1i9jnlslVo9dx+hQ5Hl9GnH/Bwy/IjxAyOePyPKYnzA== - eastasianwidth@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz" @@ -2735,13 +2628,6 @@ encodeurl@~1.0.2: resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== -end-of-stream@^1.1.0: - version "1.4.4" - resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - enquirer@^2.3.5: version "2.3.6" resolved "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz" @@ -2828,11 +2714,6 @@ escalade@^3.1.1: resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== -escape-goat@^2.0.0: - version "2.1.1" - resolved "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz" - integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== - escape-html@~1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz" @@ -3324,11 +3205,6 @@ fs.realpath@^1.0.0: resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@~2.3.2: - version "2.3.3" - resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - ftp@^0.3.10: version "0.3.10" resolved "https://registry.npmjs.org/ftp/-/ftp-0.3.10.tgz" @@ -3408,20 +3284,6 @@ get-proto@^1.0.1: dunder-proto "^1.0.1" es-object-atoms "^1.0.0" -get-stream@^4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz" - integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== - dependencies: - pump "^3.0.0" - -get-stream@^5.1.0: - version "5.2.0" - resolved "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz" - integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== - dependencies: - pump "^3.0.0" - get-stream@^6.0.0: version "6.0.1" resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz" @@ -3494,13 +3356,6 @@ global-dirs@^0.1.1: dependencies: ini "^1.3.4" -global-dirs@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz" - integrity sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA== - dependencies: - ini "2.0.0" - globals@^11.1.0: version "11.12.0" resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz" @@ -3525,24 +3380,7 @@ gopd@^1.2.0: resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz" integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== -got@^9.6.0: - version "9.6.0" - resolved "https://registry.npmjs.org/got/-/got-9.6.0.tgz" - integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== - dependencies: - "@sindresorhus/is" "^0.14.0" - "@szmarczak/http-timer" "^1.1.2" - cacheable-request "^6.0.0" - decompress-response "^3.3.0" - duplexer3 "^0.1.4" - get-stream "^4.1.0" - lowercase-keys "^1.0.1" - mimic-response "^1.0.1" - p-cancelable "^1.0.0" - to-readable-stream "^1.0.0" - url-parse-lax "^3.0.0" - -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4: +graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4: version "4.2.9" resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz" integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== @@ -3594,11 +3432,6 @@ has-unicode@^2.0.1: resolved "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz" integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== -has-yarn@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz" - integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== - has@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz" @@ -3637,11 +3470,6 @@ hosted-git-info@^4.0.1: dependencies: lru-cache "^6.0.0" -http-cache-semantics@^4.0.0: - version "4.1.0" - resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz" - integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== - http-errors@1.8.1: version "1.8.1" resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz" @@ -3732,11 +3560,6 @@ import-fresh@^3.0.0, import-fresh@^3.2.1, import-fresh@^3.3.0: parent-module "^1.0.0" resolve-from "^4.0.0" -import-lazy@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz" - integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A== - imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" @@ -3765,16 +3588,6 @@ ini@^1.3.4: resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== -ini@~1.3.0: - version "1.3.8" - resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" - integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== - -ini@2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz" - integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== - internal-slot@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz" @@ -3831,13 +3644,6 @@ is-callable@^1.1.4, is-callable@^1.2.4: resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz" integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== -is-ci@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz" - integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== - dependencies: - ci-info "^2.0.0" - is-core-module@^2.5.0, is-core-module@^2.8.0, is-core-module@^2.8.1: version "2.8.1" resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz" @@ -3874,24 +3680,11 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" -is-installed-globally@^0.4.0: - version "0.4.0" - resolved "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz" - integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== - dependencies: - global-dirs "^3.0.0" - is-path-inside "^3.0.2" - is-negative-zero@^2.0.1: version "2.0.2" resolved "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz" integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== -is-npm@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz" - integrity sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA== - is-number-object@^1.0.4: version "1.0.6" resolved "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz" @@ -3909,11 +3702,6 @@ is-obj@^2.0.0: resolved "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz" integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== -is-path-inside@^3.0.2: - version "3.0.3" - resolved "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz" - integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== - is-plain-obj@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz" @@ -3975,11 +3763,6 @@ is-text-path@^1.0.1: dependencies: text-extensions "^1.0.0" -is-typedarray@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz" - integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== - is-weakref@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz" @@ -3987,11 +3770,6 @@ is-weakref@^1.0.1: dependencies: call-bind "^1.0.2" -is-yarn-global@^0.3.0: - version "0.3.0" - resolved "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz" - integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== - isarray@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" @@ -4058,11 +3836,6 @@ jsesc@~0.5.0: resolved "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz" integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== -json-buffer@3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz" - integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ== - json-parse-even-better-errors@^2.3.0: version "2.3.1" resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz" @@ -4164,13 +3937,6 @@ jws@^3.2.2: jwa "^1.4.1" safe-buffer "^5.0.1" -keyv@^3.0.0: - version "3.1.0" - resolved "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz" - integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== - dependencies: - json-buffer "3.0.0" - kind-of@^6.0.2, kind-of@^6.0.3: version "6.0.3" resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz" @@ -4181,13 +3947,6 @@ kuler@^2.0.0: resolved "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz" integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== -latest-version@^5.1.0: - version "5.1.0" - resolved "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz" - integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== - dependencies: - package-json "^6.3.0" - levn@^0.4.1: version "0.4.1" resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz" @@ -4408,16 +4167,6 @@ long@^5.2.1: resolved "https://registry.npmjs.org/long/-/long-5.3.2.tgz" integrity sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA== -lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz" - integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== - -lowercase-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz" - integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== - lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz" @@ -4445,13 +4194,6 @@ make-dir@^2.0.0, make-dir@^2.1.0: pify "^4.0.1" semver "^5.6.0" -make-dir@^3.0.0: - version "3.1.0" - resolved "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz" - integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== - dependencies: - semver "^6.0.0" - make-dir@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz" @@ -4561,20 +4303,15 @@ mimic-fn@^4.0.0: resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz" integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== -mimic-response@^1.0.0, mimic-response@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz" - integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== - min-indent@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== -minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== +minimatch@^3.0.4, minimatch@^3.1.2: + version "3.1.5" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz" + integrity sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w== dependencies: brace-expansion "^1.1.7" @@ -4715,21 +4452,21 @@ node-releases@^2.0.1: resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz" integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA== -nodemon@^2.0.7: - version "2.0.15" - resolved "https://registry.npmjs.org/nodemon/-/nodemon-2.0.15.tgz" - integrity sha512-gdHMNx47Gw7b3kWxJV64NI+Q5nfl0y5DgDbiVtShiwa7Z0IZ07Ll4RLFo6AjrhzMtoEZn5PDE3/c2AbVsiCkpA== +nodemon@^2.0.22: + version "2.0.22" + resolved "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz" + integrity sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ== dependencies: chokidar "^3.5.2" debug "^3.2.7" ignore-by-default "^1.0.1" - minimatch "^3.0.4" + minimatch "^3.1.2" pstree.remy "^1.1.8" semver "^5.7.1" + simple-update-notifier "^1.0.7" supports-color "^5.5.0" touch "^3.1.0" undefsafe "^2.0.5" - update-notifier "^5.1.0" nopt@^5.0.0: version "5.0.0" @@ -4770,11 +4507,6 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -normalize-url@^4.1.0: - version "4.5.1" - resolved "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz" - integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== - npm-run-path@^4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz" @@ -4863,7 +4595,7 @@ on-finished@^2.3.0, on-finished@~2.3.0: dependencies: ee-first "1.1.1" -once@^1.3.0, once@^1.3.1, once@^1.4.0: +once@^1.3.0: version "1.4.0" resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== @@ -4922,11 +4654,6 @@ optionator@^0.9.1: type-check "^0.4.0" word-wrap "^1.2.3" -p-cancelable@^1.0.0: - version "1.1.0" - resolved "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz" - integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== - p-limit@^1.1.0: version "1.3.0" resolved "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz" @@ -5017,16 +4744,6 @@ pac-resolver@^5.0.0: ip "^1.1.5" netmask "^2.0.1" -package-json@^6.3.0: - version "6.5.0" - resolved "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz" - integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ== - dependencies: - got "^9.6.0" - registry-auth-token "^4.0.0" - registry-url "^5.0.0" - semver "^6.2.0" - parent-module@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" @@ -5250,11 +4967,6 @@ prelude-ls@~1.1.2: resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz" integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== -prepend-http@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz" - integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA== - 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" @@ -5335,26 +5047,11 @@ pstree.remy@^1.1.8: resolved "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz" integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - punycode@^2.1.0: version "2.1.1" resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -pupa@^2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz" - integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A== - dependencies: - escape-goat "^2.0.0" - pure-rand@^6.1.0: version "6.1.0" resolved "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz" @@ -5390,16 +5087,6 @@ raw-body@^2.2.0, raw-body@2.4.2: iconv-lite "0.4.24" unpipe "1.0.0" -rc@^1.2.8: - version "1.2.8" - resolved "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - rc9@^3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/rc9/-/rc9-3.0.1.tgz" @@ -5564,20 +5251,6 @@ regexpu-core@^4.7.1: unicode-match-property-ecmascript "^2.0.0" unicode-match-property-value-ecmascript "^2.0.0" -registry-auth-token@^4.0.0: - version "4.2.1" - resolved "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz" - integrity sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw== - dependencies: - rc "^1.2.8" - -registry-url@^5.0.0: - version "5.1.0" - resolved "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz" - integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw== - dependencies: - rc "^1.2.8" - regjsgen@^0.5.2: version "0.5.2" resolved "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz" @@ -5641,13 +5314,6 @@ resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.2 path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -responselike@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz" - integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ== - dependencies: - lowercase-keys "^1.0.0" - restore-cursor@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz" @@ -5703,13 +5369,6 @@ scheduler@^0.27.0: resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz" integrity sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q== -semver-diff@^3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz" - integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== - dependencies: - semver "^6.3.0" - semver@^5.6.0: version "5.7.1" resolved "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz" @@ -5725,7 +5384,12 @@ semver@^5.7.1: resolved "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: +semver@^6.0.0: + version "6.3.1" + resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: version "6.3.0" resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== @@ -5749,6 +5413,11 @@ semver@^7.3.5: resolved "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz" integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA== +semver@~7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz" + integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== + "semver@2 || 3 || 4 || 5": version "5.7.2" resolved "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz" @@ -5860,6 +5529,13 @@ simple-swizzle@^0.2.2: dependencies: is-arrayish "^0.3.1" +simple-update-notifier@^1.0.7: + version "1.1.0" + resolved "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz" + integrity sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg== + dependencies: + semver "~7.0.0" + slash@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz" @@ -6026,7 +5702,7 @@ string-argv@0.3.2: resolved "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz" integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q== -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -6110,11 +5786,6 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== - supports-color@^5.3.0, supports-color@^5.5.0: version "5.5.0" resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" @@ -6201,11 +5872,6 @@ to-fast-properties@^2.0.0: resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz" integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== -to-readable-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz" - integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== - to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" @@ -6321,13 +5987,6 @@ type-is@^1.6.4, type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" -typedarray-to-buffer@^3.1.5: - version "3.1.5" - resolved "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz" - integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== - dependencies: - is-typedarray "^1.0.0" - typedarray@^0.0.6: version "0.0.6" resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz" @@ -6386,13 +6045,6 @@ unicode-property-aliases-ecmascript@^2.0.0: resolved "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz" integrity sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ== -unique-string@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz" - integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== - dependencies: - crypto-random-string "^2.0.0" - universalify@^0.1.0: version "0.1.2" resolved "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz" @@ -6408,26 +6060,6 @@ unpipe@~1.0.0, unpipe@1.0.0: resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== -update-notifier@^5.1.0: - version "5.1.0" - resolved "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz" - integrity sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw== - dependencies: - boxen "^5.0.0" - chalk "^4.1.0" - configstore "^5.0.1" - has-yarn "^2.1.0" - import-lazy "^2.1.0" - is-ci "^2.0.0" - is-installed-globally "^0.4.0" - is-npm "^5.0.0" - is-yarn-global "^0.3.0" - latest-version "^5.1.0" - pupa "^2.1.1" - semver "^7.3.4" - semver-diff "^3.1.1" - xdg-basedir "^4.0.0" - uri-js@^4.2.2: version "4.4.1" resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" @@ -6435,13 +6067,6 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -url-parse-lax@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz" - integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ== - dependencies: - prepend-http "^2.0.0" - util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" @@ -6530,13 +6155,6 @@ wide-align@^1.1.2: dependencies: string-width "^1.0.2 || 2 || 3 || 4" -widest-line@^3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz" - integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== - dependencies: - string-width "^4.0.0" - winston-transport@^4.4.2: version "4.4.2" resolved "https://registry.npmjs.org/winston-transport/-/winston-transport-4.4.2.tgz" @@ -6598,21 +6216,6 @@ wrappy@1: resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -write-file-atomic@^3.0.0: - version "3.0.3" - resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz" - integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== - dependencies: - imurmurhash "^0.1.4" - is-typedarray "^1.0.0" - signal-exit "^3.0.2" - typedarray-to-buffer "^3.1.5" - -xdg-basedir@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz" - integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== - xregexp@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz" From ea27a882600b117e204caee3dbe94c16fd8504e3 Mon Sep 17 00:00:00 2001 From: DQGiac Date: Fri, 15 May 2026 15:07:24 +0700 Subject: [PATCH 02/26] feat[FI-12]: Cleaned up register and login code --- backend/src/core/modules/auth/dto/register-lawyer.dto.js | 2 ++ backend/src/core/modules/auth/dto/register-user.dto.js | 2 ++ .../modules/auth/interceptor/register-lawyer.interceptor.js | 1 + .../core/modules/auth/interceptor/register-user.interceptor.js | 1 + 4 files changed, 6 insertions(+) diff --git a/backend/src/core/modules/auth/dto/register-lawyer.dto.js b/backend/src/core/modules/auth/dto/register-lawyer.dto.js index 8e41580..a49b52b 100644 --- a/backend/src/core/modules/auth/dto/register-lawyer.dto.js +++ b/backend/src/core/modules/auth/dto/register-lawyer.dto.js @@ -3,6 +3,7 @@ import { SwaggerDocument } from "packages/swagger"; ApiDocument.addModel('RegisterLawyerDto', { role: SwaggerDocument.ApiProperty({type: 'string'}), + fullName: SwaggerDocument.ApiProperty({type: 'string'}), email: SwaggerDocument.ApiProperty({type: 'string'}), password: SwaggerDocument.ApiProperty({type: 'string'}), confirmPassword: SwaggerDocument.ApiProperty({type: 'string'}), @@ -15,6 +16,7 @@ ApiDocument.addModel('RegisterLawyerDto', { export const RegisterLawyerDto = body => ({ role: body.role, + fullName: body.fullName, email: body.email, password: body.password, confirmPassword: body.confirmPassword, diff --git a/backend/src/core/modules/auth/dto/register-user.dto.js b/backend/src/core/modules/auth/dto/register-user.dto.js index 32695a2..3bcb7c1 100644 --- a/backend/src/core/modules/auth/dto/register-user.dto.js +++ b/backend/src/core/modules/auth/dto/register-user.dto.js @@ -3,6 +3,7 @@ import { SwaggerDocument } from 'packages/swagger'; ApiDocument.addModel('RegisterUserDto', { role: SwaggerDocument.ApiProperty({ type: 'string' }), + fullName: SwaggerDocument.ApiProperty({type: 'string'}), email: SwaggerDocument.ApiProperty({ type: 'string' }), password: SwaggerDocument.ApiProperty({ type: 'string' }), confirmPassword: SwaggerDocument.ApiProperty({ type: 'string' }), @@ -10,6 +11,7 @@ ApiDocument.addModel('RegisterUserDto', { export const RegisterUserDto = body => ({ role: body.role, + fullName: body.fullName, email: body.email, password: body.password, confirmPassword: body.confirmPassword, diff --git a/backend/src/core/modules/auth/interceptor/register-lawyer.interceptor.js b/backend/src/core/modules/auth/interceptor/register-lawyer.interceptor.js index c28ba1a..4405e5b 100644 --- a/backend/src/core/modules/auth/interceptor/register-lawyer.interceptor.js +++ b/backend/src/core/modules/auth/interceptor/register-lawyer.interceptor.js @@ -5,6 +5,7 @@ import Joi from 'joi'; export const RegisterLawyerInterceptor = new DefaultValidatorInterceptor( Joi.object({ role: JoiUtils.requiredString(), + fullName: JoiUtils.requiredString(), email: JoiUtils.email().required(), password: JoiUtils.password().required(), confirmPassword: JoiUtils.password().required(), diff --git a/backend/src/core/modules/auth/interceptor/register-user.interceptor.js b/backend/src/core/modules/auth/interceptor/register-user.interceptor.js index b52dcf6..38fd188 100644 --- a/backend/src/core/modules/auth/interceptor/register-user.interceptor.js +++ b/backend/src/core/modules/auth/interceptor/register-user.interceptor.js @@ -5,6 +5,7 @@ import Joi from 'joi'; export const RegisterUserInterceptor = new DefaultValidatorInterceptor( Joi.object({ role: JoiUtils.requiredString(), + fullName: JoiUtils.requiredString(), email: JoiUtils.email().required(), password: JoiUtils.password().required(), confirmPassword: JoiUtils.password().required(), From 5c7d3ed0cad4ce24a7965e821981fcb81457baca Mon Sep 17 00:00:00 2001 From: DQGiac Date: Fri, 15 May 2026 15:23:38 +0700 Subject: [PATCH 03/26] feat[FI-12]: Cleaned up register and login code (commit 2) --- backend/src/core/api/auth/auth.resolver.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/backend/src/core/api/auth/auth.resolver.js b/backend/src/core/api/auth/auth.resolver.js index 96c2e41..7792e99 100644 --- a/backend/src/core/api/auth/auth.resolver.js +++ b/backend/src/core/api/auth/auth.resolver.js @@ -10,6 +10,13 @@ export const AuthResolver = Module.builder() module: 'AuthModule' }) .register([ + { + route: '/login', + method: 'post', + interceptors: [LoginInterceptor], + body: 'LoginDto', + controller: AuthController.login, + }, { route: '/register_user', method: 'post', From 917a5dbda3d9df05f03f7fb4b90aa61a1f14a850 Mon Sep 17 00:00:00 2001 From: DQGiac Date: Fri, 15 May 2026 15:26:38 +0700 Subject: [PATCH 04/26] feat[FI-12]: added access and refresh tokens --- .../repository/refresh-token.repository.js | 21 ++++++ .../core/modules/auth/service/auth.service.js | 74 ++++++++----------- 2 files changed, 51 insertions(+), 44 deletions(-) create mode 100644 backend/src/core/modules/auth/repository/refresh-token.repository.js diff --git a/backend/src/core/modules/auth/repository/refresh-token.repository.js b/backend/src/core/modules/auth/repository/refresh-token.repository.js new file mode 100644 index 0000000..ad729c0 --- /dev/null +++ b/backend/src/core/modules/auth/repository/refresh-token.repository.js @@ -0,0 +1,21 @@ +import { DataRepository } from 'packages/restBuilder/core/dataHandler/data.repository'; +import connection from 'core/database'; + +class Repository extends DataRepository { + async createToken(userId, token, expiresAt, trx = null) { + const result = await connection.$transaction(async (trx) => { + const insertedUser = await trx.refresh_tokens.create({ + data: { + user_id: userId, + token, + expires_at: expiresAt, + }, + select: { user_id: true, token: true } + }); + return insertedUser; + }); + return result; + } +} + +export const RefreshTokenRepository = new Repository('refresh_tokens'); \ No newline at end of file diff --git a/backend/src/core/modules/auth/service/auth.service.js b/backend/src/core/modules/auth/service/auth.service.js index 880be90..65bbd6d 100644 --- a/backend/src/core/modules/auth/service/auth.service.js +++ b/backend/src/core/modules/auth/service/auth.service.js @@ -1,3 +1,4 @@ +import crypto from 'crypto'; import connection from 'core/database'; import { pick } from 'lodash'; import { JwtPayload } from 'core/modules/auth/dto/jwt-sign.dto'; @@ -7,40 +8,18 @@ import { BcryptService } from './bcrypt.service'; import { JwtService } from './jwt.service'; import { UserRepository } from '../../user/user.repository'; import { UnAuthorizedException, DuplicateException, BadRequestException } from '../../../../packages/httpException'; +import { RefreshTokenRepository } from '../repository/refresh-token.repository'; + +const REFRESH_TOKEN_EXPIRY = 30 * 24 * 60 * 60 * 1000; class Service { constructor() { this.userRepository = UserRepository; this.jwtService = JwtService; this.bcryptService = BcryptService; + this.refreshTokenRepository = RefreshTokenRepository; } - // async registerLawyer_addon(trx, registerLawyerDto, lawyer_id) { - // await trx.lawyer_certificates.create({ - // data: { - // lawyer_id: lawyer_id, - // file_url: registerLawyerDto.licenseFile, - // issued_by: registerLawyerDto.licenseIssuer, - // issue_date: registerLawyerDto.licenseIssueDate, - // certificate_name: "placeholder" // cai nay o day de giu cho (@c quynh) - // }, - // select: { id: true } - // }); - // await trx.lawyer_details.create({ - // data: { - // user_id: lawyer_id, - // email: registerLawyerDto.email, - // password_hash: registerLawyerDto.password, - // full_name: registerLawyerDto.fullName, - // license_number: registerLawyerDto.licenseNumber, - // bar_association: registerLawyerDto.licenseIssuer, // khong biet co dung khong (@c quynh) - // is_verified: true, // (@c quynh) - // status: "AVAILABLE" // (@c quynh) - // }, - // select: { id: true } - // }); - // }; - async register(registerDto) { const existingEmail = await this.userRepository.findByEmail(registerDto.email); if (existingEmail) { @@ -61,6 +40,7 @@ class Service { registerDto.password = await this.bcryptService.hash(registerDto.password); let role_id = existingRole.id; + const result = await connection.$transaction(async (trx) => { const insertedUser = await trx.users.create({ data: { @@ -68,42 +48,34 @@ class Service { email: registerDto.email, password_hash: registerDto.password, referral_code: registerDto.referralCode, - full_name: "placeholder", // cai nay o day de giu cho (@c quynh) + full_name: registerDto.fullName, }, select: { id: true } }); - // if (registerDto.role === "LAWYER") { - // await this.registerLawyer_addon(trx, registerDto, insertedUser.id); - // } - return insertedUser; }); - // them phan access token vs refresh token sau. + const { id: ref, token: refreshToken } = await this.#createRefreshToken(result.id); + const accessToken = this.jwtService.sign(JwtPayload({ id: result.id, role: [registerDto.role] }, ref)); return { id: result.id, + full_name: registerDto.fullName, email: registerDto.email, - password_hash: registerDto.password, role_id: role_id, - referral_code: registerDto.referralCode, - // access_token: accessToken, - // refresh_token: accessTokenString, + access_token: accessToken, + refresh_token: refreshToken, }; } async login(loginDto) { const user = await this.userRepository.findByEmail(loginDto.email); - if (!user) { throw new UnAuthorizedException('Email or password is incorrect'); } - const isMatch = await this.bcryptService.compare( - loginDto.password, - user.password_hash - ); + const isMatch = await this.bcryptService.compare(loginDto.password, user.password_hash); if (!isMatch) { throw new UnAuthorizedException('Email or password is incorrect'); @@ -113,13 +85,16 @@ class Service { id: user.id, email: user.email, fullName: user.full_name, - role: user.roles?.name, + role: loginDto.role, }; + const { id: ref, token: accessTokenString } = await this.#createRefreshToken(user.id); + const accessToken = this.jwtService.sign(JwtPayload({ id: user.id, role: [loginDto.role] }, ref)); + const result = { user: userData, - access_token: this.jwtService.sign(JwtPayload(userData)), - refresh_token: this.jwtService.sign(JwtPayload(userData)), // Dummy refresh token for now + access_token: accessToken, + refresh_token: accessTokenString, }; return { @@ -127,6 +102,17 @@ class Service { message: 'Đăng nhập thành công', }; } + + async #createRefreshToken(userId) { + // Tao chuoi ngau nhien 32 bytes (64 ky tu hex) cho Refresh Token + const token = crypto.randomBytes(32).toString('hex'); + const expiresAt = new Date(Date.now() + REFRESH_TOKEN_EXPIRY); + const record = await this.refreshTokenRepository.createToken(userId, token, expiresAt); + return { + id: record.user_id || record, + token: record.token || token + }; + } } export const AuthService = new Service(); From b7bdb39b5c4b6133c61372f64f15fb89e543009b Mon Sep 17 00:00:00 2001 From: DQGiac Date: Sat, 16 May 2026 14:01:57 +0700 Subject: [PATCH 05/26] feat[FI-12]: Separated login for user and lawyer. Cleaned up code. --- backend/src/core/api/auth/auth.controller.js | 10 +++++--- backend/src/core/api/auth/auth.resolver.js | 15 ++++++++---- backend/src/core/modules/auth/dto/index.js | 3 ++- .../core/modules/auth/dto/login-lawyer.dto.js | 15 ++++++++++++ .../dto/{login.dto.js => login-user.dto.js} | 4 ++-- .../core/modules/auth/interceptor/index.js | 3 ++- .../interceptor/login-lawyer.interceptor.js | 11 +++++++++ ...terceptor.js => login-user.interceptor.js} | 2 +- .../core/modules/auth/service/auth.service.js | 23 +++++++++++++------ 9 files changed, 67 insertions(+), 19 deletions(-) create mode 100644 backend/src/core/modules/auth/dto/login-lawyer.dto.js rename backend/src/core/modules/auth/dto/{login.dto.js => login-user.dto.js} (80%) create mode 100644 backend/src/core/modules/auth/interceptor/login-lawyer.interceptor.js rename backend/src/core/modules/auth/interceptor/{login.interceptor.js => login-user.interceptor.js} (78%) diff --git a/backend/src/core/api/auth/auth.controller.js b/backend/src/core/api/auth/auth.controller.js index 4a94cbf..55df45b 100644 --- a/backend/src/core/api/auth/auth.controller.js +++ b/backend/src/core/api/auth/auth.controller.js @@ -1,6 +1,6 @@ import { AuthService } from '../../modules/auth/service/auth.service'; import { RegisterUserDto, RegisterLawyerDto } from '../../modules/auth'; -import { LoginDto } from '../../modules/auth'; +import { LoginUserDto, LoginLawyerDto } from '../../modules/auth'; import { ValidHttpResponse } from '../../../packages/handler/response/validHttp.response'; class Controller { @@ -18,8 +18,12 @@ class Controller { return ValidHttpResponse.toOkResponse(data); } - login = async req => { - const data = await this.service.login(LoginDto(req.body)); + loginUser = async req => { + const data = await this.service.login(LoginUserDto(req.body)); + return ValidHttpResponse.toOkResponse(data); + } + loginLawyer = async req => { + const data = await this.service.login(LoginLawyerDto(req.body)); return ValidHttpResponse.toOkResponse(data); } } diff --git a/backend/src/core/api/auth/auth.resolver.js b/backend/src/core/api/auth/auth.resolver.js index 7792e99..ba72d1e 100644 --- a/backend/src/core/api/auth/auth.resolver.js +++ b/backend/src/core/api/auth/auth.resolver.js @@ -1,4 +1,4 @@ -import { LoginInterceptor, RegisterUserDto, RegisterLawyerDto } from 'core/modules/auth'; +import { LoginUserInterceptor, LoginLawyerInterceptor, RegisterUserDto, RegisterLawyerDto } from 'core/modules/auth'; import { Module } from 'packages/handler/Module'; import { AuthController } from './auth.controller'; import { RegisterLawyerInterceptor, RegisterUserInterceptor } from 'core/modules/auth/'; @@ -13,9 +13,16 @@ export const AuthResolver = Module.builder() { route: '/login', method: 'post', - interceptors: [LoginInterceptor], - body: 'LoginDto', - controller: AuthController.login, + interceptors: [LoginUserInterceptor], + body: 'LoginUserDto', + controller: AuthController.loginUser, + }, + { + route: '/login', + method: 'post', + interceptors: [LoginLawyerInterceptor], + body: 'LoginLawyerDto', + controller: AuthController.loginLawyer, }, { route: '/register_user', diff --git a/backend/src/core/modules/auth/dto/index.js b/backend/src/core/modules/auth/dto/index.js index fe239d5..520d556 100644 --- a/backend/src/core/modules/auth/dto/index.js +++ b/backend/src/core/modules/auth/dto/index.js @@ -1,4 +1,5 @@ -export * from './login.dto'; +export * from './login-user.dto'; +export * from './login-lawyer.dto'; export * from './register-user.dto'; export * from './register-lawyer.dto'; export * from './jwt-sign.dto'; diff --git a/backend/src/core/modules/auth/dto/login-lawyer.dto.js b/backend/src/core/modules/auth/dto/login-lawyer.dto.js new file mode 100644 index 0000000..7c80477 --- /dev/null +++ b/backend/src/core/modules/auth/dto/login-lawyer.dto.js @@ -0,0 +1,15 @@ +import { ApiDocument } from 'core/config/swagger.config'; +import { SwaggerDocument } from 'packages/swagger'; + +ApiDocument.addModel('LoginLawyerDto', + { + email: SwaggerDocument.ApiProperty({ type: 'string' }), + password: SwaggerDocument.ApiProperty({ type: 'string' }), + licenseNumber: SwaggerDocument.ApiProperty({ type: 'string' }) + }); + +export const LoginLawyerDto = body => ({ + email: body.email, + password: body.password, + licenseNumber: body.licenseNumber, +}); diff --git a/backend/src/core/modules/auth/dto/login.dto.js b/backend/src/core/modules/auth/dto/login-user.dto.js similarity index 80% rename from backend/src/core/modules/auth/dto/login.dto.js rename to backend/src/core/modules/auth/dto/login-user.dto.js index 2e28ce0..ddfe475 100644 --- a/backend/src/core/modules/auth/dto/login.dto.js +++ b/backend/src/core/modules/auth/dto/login-user.dto.js @@ -1,13 +1,13 @@ import { ApiDocument } from 'core/config/swagger.config'; import { SwaggerDocument } from 'packages/swagger'; -ApiDocument.addModel('LoginDto', +ApiDocument.addModel('LoginUserDto', { email: SwaggerDocument.ApiProperty({ type: 'string' }), password: SwaggerDocument.ApiProperty({ type: 'string' }) }); -export const LoginDto = body => ({ +export const LoginUserDto = body => ({ email: body.email, password: body.password }); diff --git a/backend/src/core/modules/auth/interceptor/index.js b/backend/src/core/modules/auth/interceptor/index.js index 7654dec..38363f5 100644 --- a/backend/src/core/modules/auth/interceptor/index.js +++ b/backend/src/core/modules/auth/interceptor/index.js @@ -1,3 +1,4 @@ -export * from './login.interceptor'; +export * from './login-user.interceptor'; +export * from './login-lawyer.interceptor'; export * from './register-lawyer.interceptor'; export * from './register-user.interceptor'; diff --git a/backend/src/core/modules/auth/interceptor/login-lawyer.interceptor.js b/backend/src/core/modules/auth/interceptor/login-lawyer.interceptor.js new file mode 100644 index 0000000..63ade69 --- /dev/null +++ b/backend/src/core/modules/auth/interceptor/login-lawyer.interceptor.js @@ -0,0 +1,11 @@ +import { DefaultValidatorInterceptor } from 'core/infrastructure/interceptor'; +import { JoiUtils } from 'core/utils'; +import Joi from 'joi'; + +export const LoginLawyerInterceptor = new DefaultValidatorInterceptor( + Joi.object({ + email: JoiUtils.email().required(), + password: JoiUtils.requiredString(), + licenseNumber: JoiUtils.requiredString(), + }) +); diff --git a/backend/src/core/modules/auth/interceptor/login.interceptor.js b/backend/src/core/modules/auth/interceptor/login-user.interceptor.js similarity index 78% rename from backend/src/core/modules/auth/interceptor/login.interceptor.js rename to backend/src/core/modules/auth/interceptor/login-user.interceptor.js index 64ae376..0340f20 100644 --- a/backend/src/core/modules/auth/interceptor/login.interceptor.js +++ b/backend/src/core/modules/auth/interceptor/login-user.interceptor.js @@ -2,7 +2,7 @@ import { DefaultValidatorInterceptor } from 'core/infrastructure/interceptor'; import { JoiUtils } from 'core/utils'; import Joi from 'joi'; -export const LoginInterceptor = new DefaultValidatorInterceptor( +export const LoginUserInterceptor = new DefaultValidatorInterceptor( Joi.object({ email: JoiUtils.email().required(), password: JoiUtils.requiredString(), diff --git a/backend/src/core/modules/auth/service/auth.service.js b/backend/src/core/modules/auth/service/auth.service.js index 65bbd6d..a764179 100644 --- a/backend/src/core/modules/auth/service/auth.service.js +++ b/backend/src/core/modules/auth/service/auth.service.js @@ -34,7 +34,7 @@ class Service { where: {name: registerDto.role} }); if (!existingRole) { - throw new BadRequestException("Role does not exist"); + throw new BadRequestException("Role must be one of: USER, LAWYER, ADMIN"); } registerDto.password = await this.bcryptService.hash(registerDto.password); @@ -56,8 +56,8 @@ class Service { return insertedUser; }); - const { id: ref, token: refreshToken } = await this.#createRefreshToken(result.id); - const accessToken = this.jwtService.sign(JwtPayload({ id: result.id, role: [registerDto.role] }, ref)); + const { token: refreshToken } = await this.#createRefreshToken(result.id); + const accessToken = this.jwtService.sign(JwtPayload({ id: result.id, role: [registerDto.role] })); return { id: result.id, @@ -81,6 +81,16 @@ class Service { throw new UnAuthorizedException('Email or password is incorrect'); } + if (loginDto.role === "LAWYER") { + let existingLicenseNumber = await connection.lawyer_details.findFirst({ + where: {user_id: user.id} + }); + + if (existingLicenseNumber.license_number !== loginDto.license_number) { + throw new UnAuthorizedException('Invalid credentials') + } + } + const userData = { id: user.id, email: user.email, @@ -88,13 +98,13 @@ class Service { role: loginDto.role, }; - const { id: ref, token: accessTokenString } = await this.#createRefreshToken(user.id); - const accessToken = this.jwtService.sign(JwtPayload({ id: user.id, role: [loginDto.role] }, ref)); + const { token: refreshToken } = await this.#createRefreshToken(user.id); + const accessToken = this.jwtService.sign(JwtPayload({ id: user.id, role: [loginDto.role] })); const result = { user: userData, access_token: accessToken, - refresh_token: accessTokenString, + refresh_token: refreshToken, }; return { @@ -104,7 +114,6 @@ class Service { } async #createRefreshToken(userId) { - // Tao chuoi ngau nhien 32 bytes (64 ky tu hex) cho Refresh Token const token = crypto.randomBytes(32).toString('hex'); const expiresAt = new Date(Date.now() + REFRESH_TOKEN_EXPIRY); const record = await this.refreshTokenRepository.createToken(userId, token, expiresAt); From f948f5cf595873b69108b00e11f7f546bfd5c334 Mon Sep 17 00:00:00 2001 From: DQGiac Date: Mon, 18 May 2026 00:09:35 +0700 Subject: [PATCH 06/26] feat[FI-12]: Added forgot password and verify OTP. Cleaned up code from login and register. --- backend/src/core/api/auth/auth.controller.js | 16 +++- backend/src/core/api/auth/auth.resolver.js | 30 +++++-- .../modules/auth/dto/forgot-password.dto.js | 10 +++ backend/src/core/modules/auth/dto/index.js | 2 + .../core/modules/auth/dto/verify-otp.dto.js | 12 +++ backend/src/core/modules/auth/index.js | 1 + .../forgot-password.interceptor.js | 9 ++ .../core/modules/auth/interceptor/index.js | 2 + .../interceptor/verify-otp.interceptor.js | 10 +++ .../repository/forgot-password.repository.js | 19 ++++ .../src/core/modules/auth/repository/index.js | 3 + .../repository/password-reset.repository.js | 19 ++++ .../repository/refresh-token.repository.js | 18 ++-- .../core/modules/auth/service/auth.service.js | 89 +++++++++++++++---- 14 files changed, 206 insertions(+), 34 deletions(-) create mode 100644 backend/src/core/modules/auth/dto/forgot-password.dto.js create mode 100644 backend/src/core/modules/auth/dto/verify-otp.dto.js create mode 100644 backend/src/core/modules/auth/interceptor/forgot-password.interceptor.js create mode 100644 backend/src/core/modules/auth/interceptor/verify-otp.interceptor.js create mode 100644 backend/src/core/modules/auth/repository/forgot-password.repository.js create mode 100644 backend/src/core/modules/auth/repository/index.js create mode 100644 backend/src/core/modules/auth/repository/password-reset.repository.js diff --git a/backend/src/core/api/auth/auth.controller.js b/backend/src/core/api/auth/auth.controller.js index 55df45b..c10e304 100644 --- a/backend/src/core/api/auth/auth.controller.js +++ b/backend/src/core/api/auth/auth.controller.js @@ -1,6 +1,5 @@ import { AuthService } from '../../modules/auth/service/auth.service'; -import { RegisterUserDto, RegisterLawyerDto } from '../../modules/auth'; -import { LoginUserDto, LoginLawyerDto } from '../../modules/auth'; +import { RegisterUserDto, RegisterLawyerDto, LoginUserDto, LoginLawyerDto, ForgotPasswordDto, VerifyOtpDto } from '../../modules/auth'; import { ValidHttpResponse } from '../../../packages/handler/response/validHttp.response'; class Controller { @@ -26,6 +25,19 @@ class Controller { const data = await this.service.login(LoginLawyerDto(req.body)); return ValidHttpResponse.toOkResponse(data); } + + forgotPassword = async req => { + const data = await this.service.forgotPassword(ForgotPasswordDto(req.body)); + return ValidHttpResponse.toOkResponse(data); + } + verifyOtp = async req => { + const data = await this.service.verifyOtp(VerifyOtpDto(req.body)); + return ValidHttpResponse.toOkResponse(data); + } + // passwordReset = async req => { + // const data = await this.service.passwordReset(passwordResetDto(req.body)); + // return ValidHttpResponse.toOkResponse(data); + // } } export const AuthController = new Controller(); diff --git a/backend/src/core/api/auth/auth.resolver.js b/backend/src/core/api/auth/auth.resolver.js index ba72d1e..4838b3d 100644 --- a/backend/src/core/api/auth/auth.resolver.js +++ b/backend/src/core/api/auth/auth.resolver.js @@ -1,7 +1,13 @@ -import { LoginUserInterceptor, LoginLawyerInterceptor, RegisterUserDto, RegisterLawyerDto } from 'core/modules/auth'; +import { + LoginUserInterceptor, + LoginLawyerInterceptor, + RegisterUserInterceptor, + RegisterLawyerInterceptor, + ForgotPasswordInterceptor, + VerifyOtpInterceptor, +} from 'core/modules/auth'; import { Module } from 'packages/handler/Module'; import { AuthController } from './auth.controller'; -import { RegisterLawyerInterceptor, RegisterUserInterceptor } from 'core/modules/auth/'; export const AuthResolver = Module.builder() .addPrefix({ @@ -11,14 +17,14 @@ export const AuthResolver = Module.builder() }) .register([ { - route: '/login', + route: '/login_user', method: 'post', interceptors: [LoginUserInterceptor], body: 'LoginUserDto', controller: AuthController.loginUser, }, { - route: '/login', + route: '/login_lawyer', method: 'post', interceptors: [LoginLawyerInterceptor], body: 'LoginLawyerDto', @@ -37,5 +43,19 @@ export const AuthResolver = Module.builder() interceptors: [RegisterLawyerInterceptor], body: 'RegisterLawyerDto', controller: AuthController.registerLawyer, - } + }, + { + route: '/forgot_password', + method: 'post', + interceptors: [ForgotPasswordInterceptor], + body: 'ForgotPasswordDto', + controller: AuthController.forgotPassword, + }, + { + route: '/verify_otp', + method: 'post', + interceptors: [VerifyOtpInterceptor], + body: 'VerifyOtpDto', + controller: AuthController.verifyOtp, + }, ]); diff --git a/backend/src/core/modules/auth/dto/forgot-password.dto.js b/backend/src/core/modules/auth/dto/forgot-password.dto.js new file mode 100644 index 0000000..958d28c --- /dev/null +++ b/backend/src/core/modules/auth/dto/forgot-password.dto.js @@ -0,0 +1,10 @@ +import { ApiDocument } from 'core/config/swagger.config'; +import { SwaggerDocument } from 'packages/swagger'; + +ApiDocument.addModel('ForgotPasswordDto', { + email: SwaggerDocument.ApiProperty({ type: 'string' }), +}); + +export const ForgotPasswordDto = body => ({ + email: body.email, +}); \ No newline at end of file diff --git a/backend/src/core/modules/auth/dto/index.js b/backend/src/core/modules/auth/dto/index.js index 520d556..e98a4e8 100644 --- a/backend/src/core/modules/auth/dto/index.js +++ b/backend/src/core/modules/auth/dto/index.js @@ -3,3 +3,5 @@ export * from './login-lawyer.dto'; export * from './register-user.dto'; export * from './register-lawyer.dto'; export * from './jwt-sign.dto'; +export * from './forgot-password.dto'; +export * from './verify-otp.dto'; diff --git a/backend/src/core/modules/auth/dto/verify-otp.dto.js b/backend/src/core/modules/auth/dto/verify-otp.dto.js new file mode 100644 index 0000000..5de71c7 --- /dev/null +++ b/backend/src/core/modules/auth/dto/verify-otp.dto.js @@ -0,0 +1,12 @@ +import { ApiDocument } from 'core/config/swagger.config'; +import { SwaggerDocument } from 'packages/swagger'; + +ApiDocument.addModel('VerifyOtpDto', { + email: SwaggerDocument.ApiProperty({ type: 'string' }), + otp: SwaggerDocument.ApiProperty({ type: 'string' }), +}); + +export const VerifyOtpDto = body => ({ + email: body.email, + otp: body.otp, +}); \ No newline at end of file diff --git a/backend/src/core/modules/auth/index.js b/backend/src/core/modules/auth/index.js index 646f1aa..028de5f 100644 --- a/backend/src/core/modules/auth/index.js +++ b/backend/src/core/modules/auth/index.js @@ -1,3 +1,4 @@ export * from './dto'; export * from './service'; export * from './interceptor'; +export * from './repository'; \ No newline at end of file diff --git a/backend/src/core/modules/auth/interceptor/forgot-password.interceptor.js b/backend/src/core/modules/auth/interceptor/forgot-password.interceptor.js new file mode 100644 index 0000000..a4b0d75 --- /dev/null +++ b/backend/src/core/modules/auth/interceptor/forgot-password.interceptor.js @@ -0,0 +1,9 @@ +import { DefaultValidatorInterceptor } from 'core/infrastructure/interceptor'; +import { JoiUtils } from 'core/utils'; +import Joi from 'joi'; + +export const ForgotPasswordInterceptor = new DefaultValidatorInterceptor( + Joi.object({ + email: JoiUtils.email().required(), + }) +); diff --git a/backend/src/core/modules/auth/interceptor/index.js b/backend/src/core/modules/auth/interceptor/index.js index 38363f5..e4253ad 100644 --- a/backend/src/core/modules/auth/interceptor/index.js +++ b/backend/src/core/modules/auth/interceptor/index.js @@ -2,3 +2,5 @@ export * from './login-user.interceptor'; export * from './login-lawyer.interceptor'; export * from './register-lawyer.interceptor'; export * from './register-user.interceptor'; +export * from './forgot-password.interceptor'; +export * from './verify-otp.interceptor'; diff --git a/backend/src/core/modules/auth/interceptor/verify-otp.interceptor.js b/backend/src/core/modules/auth/interceptor/verify-otp.interceptor.js new file mode 100644 index 0000000..dc02809 --- /dev/null +++ b/backend/src/core/modules/auth/interceptor/verify-otp.interceptor.js @@ -0,0 +1,10 @@ +import { DefaultValidatorInterceptor } from 'core/infrastructure/interceptor'; +import { JoiUtils } from 'core/utils'; +import Joi from 'joi'; + +export const VerifyOtpInterceptor = new DefaultValidatorInterceptor( + Joi.object({ + email: JoiUtils.email().required(), + otp: JoiUtils.requiredString(), + }) +); \ No newline at end of file diff --git a/backend/src/core/modules/auth/repository/forgot-password.repository.js b/backend/src/core/modules/auth/repository/forgot-password.repository.js new file mode 100644 index 0000000..421de97 --- /dev/null +++ b/backend/src/core/modules/auth/repository/forgot-password.repository.js @@ -0,0 +1,19 @@ +import connection from 'core/database'; +import { DataRepository } from 'packages/restBuilder/core/dataHandler'; + +class Repository extends DataRepository { + async forgotPassword(userId, token, expiresAt, trx = null) { + return await connection.users.update({ + where: { + id: userId, + }, + data: { + password_reset_token: token, // chỗ này em bỏ vào password_reset_token vì không có cái column riêng cho otp thường + password_reset_expiry: expiresAt, // tương tự + // cái này chắc ảnh hưởng bảo mật nhiều, nhưng mà em không rõ có cách nào khác. (@c quynh) + }, + }); + }; +} + +export const ForgotPasswordRepository = new Repository('users'); \ No newline at end of file diff --git a/backend/src/core/modules/auth/repository/index.js b/backend/src/core/modules/auth/repository/index.js new file mode 100644 index 0000000..efefad1 --- /dev/null +++ b/backend/src/core/modules/auth/repository/index.js @@ -0,0 +1,3 @@ +export * from './refresh-token.repository'; +export * from './forgot-password.repository'; +export * from './password-reset.repository'; \ No newline at end of file diff --git a/backend/src/core/modules/auth/repository/password-reset.repository.js b/backend/src/core/modules/auth/repository/password-reset.repository.js new file mode 100644 index 0000000..b4a58a5 --- /dev/null +++ b/backend/src/core/modules/auth/repository/password-reset.repository.js @@ -0,0 +1,19 @@ +// import { UserRepository } from 'core/modules/user/user.repository'; +import connection from 'core/database'; +import { DataRepository } from 'packages/restBuilder/core/dataHandler'; + +class Repository extends DataRepository { + async passwordReset(userId, token, expiresAt, trx = null) { + return await connection.users.update({ + where: { + id: userId, + }, + data: { + password_reset_token: token, + password_reset_expiry: expiresAt, + }, + }); + }; +} + +export const PasswordResetRepository = new Repository('users'); \ No newline at end of file diff --git a/backend/src/core/modules/auth/repository/refresh-token.repository.js b/backend/src/core/modules/auth/repository/refresh-token.repository.js index ad729c0..b389fc6 100644 --- a/backend/src/core/modules/auth/repository/refresh-token.repository.js +++ b/backend/src/core/modules/auth/repository/refresh-token.repository.js @@ -3,18 +3,14 @@ import connection from 'core/database'; class Repository extends DataRepository { async createToken(userId, token, expiresAt, trx = null) { - const result = await connection.$transaction(async (trx) => { - const insertedUser = await trx.refresh_tokens.create({ - data: { - user_id: userId, - token, - expires_at: expiresAt, - }, - select: { user_id: true, token: true } - }); - return insertedUser; + return await connection.refresh_tokens.create({ + data: { + user_id: userId, + token, + expires_at: expiresAt, + }, + select: { user_id: true, token: true }, }); - return result; } } diff --git a/backend/src/core/modules/auth/service/auth.service.js b/backend/src/core/modules/auth/service/auth.service.js index a764179..d54625b 100644 --- a/backend/src/core/modules/auth/service/auth.service.js +++ b/backend/src/core/modules/auth/service/auth.service.js @@ -8,9 +8,11 @@ import { BcryptService } from './bcrypt.service'; import { JwtService } from './jwt.service'; import { UserRepository } from '../../user/user.repository'; import { UnAuthorizedException, DuplicateException, BadRequestException } from '../../../../packages/httpException'; -import { RefreshTokenRepository } from '../repository/refresh-token.repository'; +import { RefreshTokenRepository, PasswordResetRepository, ForgotPasswordRepository } from '../repository'; -const REFRESH_TOKEN_EXPIRY = 30 * 24 * 60 * 60 * 1000; +const REFRESH_TOKEN_EXPIRY = 24 * 60 * 60 * 1000; // 1 day +const FORGOT_PASSWORD_TOKEN_EXPIRY = 15 * 60 * 1000; // 15 minute +const PASSWORD_RESET_TOKEN_EXPIRY = 15 * 60 * 1000; // 15 minute class Service { constructor() { @@ -18,6 +20,8 @@ class Service { this.jwtService = JwtService; this.bcryptService = BcryptService; this.refreshTokenRepository = RefreshTokenRepository; + this.passwordResetRepository = PasswordResetRepository; + this.forgotPasswordRepository = ForgotPasswordRepository; } async register(registerDto) { @@ -41,19 +45,15 @@ class Service { let role_id = existingRole.id; - const result = await connection.$transaction(async (trx) => { - const insertedUser = await trx.users.create({ - data: { - role_id: role_id, - email: registerDto.email, - password_hash: registerDto.password, - referral_code: registerDto.referralCode, - full_name: registerDto.fullName, - }, - select: { id: true } - }); - - return insertedUser; + const result = await connection.users.create({ + data: { + role_id: role_id, + email: registerDto.email, + password_hash: registerDto.password, + referral_code: registerDto.referralCode, + full_name: registerDto.fullName, + }, + select: { id: true } }); const { token: refreshToken } = await this.#createRefreshToken(result.id); @@ -109,7 +109,54 @@ class Service { return { data: result, - message: 'Đăng nhập thành công', + message: "Login successful.", + }; + } + + async forgotPassword(forgotPasswordDto) { + const existingEmail = await this.userRepository.findByEmail(forgotPasswordDto.email); + + if (!existingEmail) { + throw new BadRequestException("Email does not exist"); + } + + const otp = crypto.randomInt(100000, 999999).toString(); + const expiresAt = new Date(Date.now() + 15*60*1000); + + console.log(new Date(Date.now() + 15*60*1000)); + + await this.forgotPasswordRepository.forgotPassword(existingEmail.id, otp, expiresAt); + + return { + message: "OTP has been sent to your email.", + }; + } + + async verifyOtp(verifyOtpDto) { + const existingEmail = await this.userRepository.findByEmail(verifyOtpDto.email); + if (!existingEmail) { + throw new BadRequestException("Email does not exist"); + } + + const existingOtp = await connection.users.findFirst({ + where: { id: existingEmail.id }, + select: { password_reset_token: true, password_reset_expiry: true } + }); + + if (existingOtp.password_reset_expiry < new Date(Date.now())) { + throw new UnAuthorizedException("OTP has expired " + (Date.now()).toString()); + } + + if (existingOtp.password_reset_token != verifyOtpDto.otp) { + throw new UnAuthorizedException("Invalid OTP"); + } + + const { token: passwordResetToken } = await this.#createPasswordResetToken(existingEmail.id); + + return { + data: { + password_reset_token: passwordResetToken, + } }; } @@ -122,6 +169,16 @@ class Service { token: record.token || token }; } + + async #createPasswordResetToken(userId) { + const token = crypto.randomBytes(32).toString('hex'); + const expiresAt = new Date(Date.now() + PASSWORD_RESET_TOKEN_EXPIRY); + const record = await this.passwordResetRepository.passwordReset(userId, token, expiresAt); + return { + id: record.user_id || record, + token: record.token || token + }; + } } export const AuthService = new Service(); From 5169e4d59bb617a7ac191c1671480ccc2d0a2930 Mon Sep 17 00:00:00 2001 From: DQGiac Date: Mon, 18 May 2026 09:19:53 +0700 Subject: [PATCH 07/26] feat[FI-12]: Added reset password. Changed interceptors to not accept invalid password. Cleaned up code. --- backend/src/core/api/auth/auth.controller.js | 10 ++-- backend/src/core/api/auth/auth.resolver.js | 8 +++ backend/src/core/modules/auth/dto/index.js | 1 + .../modules/auth/dto/reset-password.dto.js | 14 +++++ .../core/modules/auth/interceptor/index.js | 1 + .../interceptor/login-lawyer.interceptor.js | 2 +- .../interceptor/login-user.interceptor.js | 2 +- .../register-lawyer.interceptor.js | 9 ++- .../interceptor/register-user.interceptor.js | 9 ++- .../interceptor/reset-password.interceptor.js | 16 ++++++ .../repository/password-reset.repository.js | 2 +- .../core/modules/auth/service/auth.service.js | 56 ++++++++++++------- 12 files changed, 98 insertions(+), 32 deletions(-) create mode 100644 backend/src/core/modules/auth/dto/reset-password.dto.js create mode 100644 backend/src/core/modules/auth/interceptor/reset-password.interceptor.js diff --git a/backend/src/core/api/auth/auth.controller.js b/backend/src/core/api/auth/auth.controller.js index c10e304..db64eea 100644 --- a/backend/src/core/api/auth/auth.controller.js +++ b/backend/src/core/api/auth/auth.controller.js @@ -1,5 +1,5 @@ import { AuthService } from '../../modules/auth/service/auth.service'; -import { RegisterUserDto, RegisterLawyerDto, LoginUserDto, LoginLawyerDto, ForgotPasswordDto, VerifyOtpDto } from '../../modules/auth'; +import { RegisterUserDto, RegisterLawyerDto, LoginUserDto, LoginLawyerDto, ForgotPasswordDto, VerifyOtpDto, ResetPasswordDto } from '../../modules/auth'; import { ValidHttpResponse } from '../../../packages/handler/response/validHttp.response'; class Controller { @@ -34,10 +34,10 @@ class Controller { const data = await this.service.verifyOtp(VerifyOtpDto(req.body)); return ValidHttpResponse.toOkResponse(data); } - // passwordReset = async req => { - // const data = await this.service.passwordReset(passwordResetDto(req.body)); - // return ValidHttpResponse.toOkResponse(data); - // } + resetPassword = async req => { + const data = await this.service.resetPassword(ResetPasswordDto(req.body)); + return ValidHttpResponse.toOkResponse(data); + } } export const AuthController = new Controller(); diff --git a/backend/src/core/api/auth/auth.resolver.js b/backend/src/core/api/auth/auth.resolver.js index 4838b3d..5dd91ca 100644 --- a/backend/src/core/api/auth/auth.resolver.js +++ b/backend/src/core/api/auth/auth.resolver.js @@ -5,6 +5,7 @@ import { RegisterLawyerInterceptor, ForgotPasswordInterceptor, VerifyOtpInterceptor, + ResetPasswordInterceptor, } from 'core/modules/auth'; import { Module } from 'packages/handler/Module'; import { AuthController } from './auth.controller'; @@ -58,4 +59,11 @@ export const AuthResolver = Module.builder() body: 'VerifyOtpDto', controller: AuthController.verifyOtp, }, + { + route: '/reset-password', + method: 'post', + interceptors: [ResetPasswordInterceptor], + body: 'ResetPasswordDto', + controller: AuthController.resetPassword, + }, ]); diff --git a/backend/src/core/modules/auth/dto/index.js b/backend/src/core/modules/auth/dto/index.js index e98a4e8..1c101aa 100644 --- a/backend/src/core/modules/auth/dto/index.js +++ b/backend/src/core/modules/auth/dto/index.js @@ -5,3 +5,4 @@ export * from './register-lawyer.dto'; export * from './jwt-sign.dto'; export * from './forgot-password.dto'; export * from './verify-otp.dto'; +export * from './reset-password.dto'; diff --git a/backend/src/core/modules/auth/dto/reset-password.dto.js b/backend/src/core/modules/auth/dto/reset-password.dto.js new file mode 100644 index 0000000..83e6a91 --- /dev/null +++ b/backend/src/core/modules/auth/dto/reset-password.dto.js @@ -0,0 +1,14 @@ +import { ApiDocument } from 'core/config/swagger.config'; +import { SwaggerDocument } from 'packages/swagger'; + +ApiDocument.addModel('ResetPasswordDto', { + token: SwaggerDocument.ApiProperty({ type: 'string' }), + newPassword: SwaggerDocument.ApiProperty({ type: 'string' }), + confirmNewPassword: SwaggerDocument.ApiProperty({ type: 'string' }), +}); + +export const ResetPasswordDto = body => ({ + token: body.token, + newPassword: body.newPassword, + confirmNewPassword: body.confirmNewPassword, +}); \ No newline at end of file diff --git a/backend/src/core/modules/auth/interceptor/index.js b/backend/src/core/modules/auth/interceptor/index.js index e4253ad..87dae84 100644 --- a/backend/src/core/modules/auth/interceptor/index.js +++ b/backend/src/core/modules/auth/interceptor/index.js @@ -4,3 +4,4 @@ export * from './register-lawyer.interceptor'; export * from './register-user.interceptor'; export * from './forgot-password.interceptor'; export * from './verify-otp.interceptor'; +export * from './reset-password.interceptor'; diff --git a/backend/src/core/modules/auth/interceptor/login-lawyer.interceptor.js b/backend/src/core/modules/auth/interceptor/login-lawyer.interceptor.js index 63ade69..e3a1882 100644 --- a/backend/src/core/modules/auth/interceptor/login-lawyer.interceptor.js +++ b/backend/src/core/modules/auth/interceptor/login-lawyer.interceptor.js @@ -5,7 +5,7 @@ import Joi from 'joi'; export const LoginLawyerInterceptor = new DefaultValidatorInterceptor( Joi.object({ email: JoiUtils.email().required(), - password: JoiUtils.requiredString(), + password: JoiUtils.password().required(), licenseNumber: JoiUtils.requiredString(), }) ); diff --git a/backend/src/core/modules/auth/interceptor/login-user.interceptor.js b/backend/src/core/modules/auth/interceptor/login-user.interceptor.js index 0340f20..6052bdd 100644 --- a/backend/src/core/modules/auth/interceptor/login-user.interceptor.js +++ b/backend/src/core/modules/auth/interceptor/login-user.interceptor.js @@ -5,6 +5,6 @@ import Joi from 'joi'; export const LoginUserInterceptor = new DefaultValidatorInterceptor( Joi.object({ email: JoiUtils.email().required(), - password: JoiUtils.requiredString(), + password: JoiUtils.password().required(), }) ); diff --git a/backend/src/core/modules/auth/interceptor/register-lawyer.interceptor.js b/backend/src/core/modules/auth/interceptor/register-lawyer.interceptor.js index 4405e5b..5120178 100644 --- a/backend/src/core/modules/auth/interceptor/register-lawyer.interceptor.js +++ b/backend/src/core/modules/auth/interceptor/register-lawyer.interceptor.js @@ -7,8 +7,13 @@ export const RegisterLawyerInterceptor = new DefaultValidatorInterceptor( role: JoiUtils.requiredString(), fullName: JoiUtils.requiredString(), email: JoiUtils.email().required(), - password: JoiUtils.password().required(), - confirmPassword: JoiUtils.password().required(), + password: JoiUtils.password().min(8).pattern(/^(?=.*[a-zA-Z])(?=.*\d).{8,}$/).required().messages({ + 'string.min': 'Password must be at least 8 characters', + 'string.pattern.base': 'Password must contain at least one letter and one number', + }), + confirmPassword: JoiUtils.password().valid(Joi.ref('password')).required().messages({ + 'any.only': 'Passwords do not match.', + }), licenseNumber: JoiUtils.requiredString(), licenseIssuer: JoiUtils.requiredString(), licenseIssueDate: JoiUtils.requiredString().isoDate(), // e khong ro cai format date diff --git a/backend/src/core/modules/auth/interceptor/register-user.interceptor.js b/backend/src/core/modules/auth/interceptor/register-user.interceptor.js index 38fd188..30f49ea 100644 --- a/backend/src/core/modules/auth/interceptor/register-user.interceptor.js +++ b/backend/src/core/modules/auth/interceptor/register-user.interceptor.js @@ -7,7 +7,12 @@ export const RegisterUserInterceptor = new DefaultValidatorInterceptor( role: JoiUtils.requiredString(), fullName: JoiUtils.requiredString(), email: JoiUtils.email().required(), - password: JoiUtils.password().required(), - confirmPassword: JoiUtils.password().required(), + password: JoiUtils.password().min(8).pattern(/^(?=.*[a-zA-Z])(?=.*\d).{8,}$/).required().messages({ + 'string.min': 'Password must be at least 8 characters', + 'string.pattern.base': 'Password must contain at least one letter and one number', + }), + confirmPassword: JoiUtils.password().valid(Joi.ref('password')).required().messages({ + 'any.only': 'Passwords do not match.', + }), }) ); \ No newline at end of file diff --git a/backend/src/core/modules/auth/interceptor/reset-password.interceptor.js b/backend/src/core/modules/auth/interceptor/reset-password.interceptor.js new file mode 100644 index 0000000..e5066dc --- /dev/null +++ b/backend/src/core/modules/auth/interceptor/reset-password.interceptor.js @@ -0,0 +1,16 @@ +import { DefaultValidatorInterceptor } from 'core/infrastructure/interceptor'; +import { JoiUtils } from 'core/utils'; +import Joi from 'joi'; + +export const ResetPasswordInterceptor = new DefaultValidatorInterceptor( + Joi.object({ + token: JoiUtils.requiredString(), + newPassword: JoiUtils.password().min(8).pattern(/^(?=.*[a-zA-Z])(?=.*\d).{8,}$/).required().messages({ + 'string.min': 'Password must be at least 8 characters', + 'string.pattern.base': 'Password must contain at least one letter and one number', + }), + confirmNewPassword: JoiUtils.password().valid(Joi.ref('newPassword')).required().messages({ + 'any.only': 'Passwords do not match.', + }), + }) +); \ No newline at end of file diff --git a/backend/src/core/modules/auth/repository/password-reset.repository.js b/backend/src/core/modules/auth/repository/password-reset.repository.js index b4a58a5..ecea31a 100644 --- a/backend/src/core/modules/auth/repository/password-reset.repository.js +++ b/backend/src/core/modules/auth/repository/password-reset.repository.js @@ -3,7 +3,7 @@ import connection from 'core/database'; import { DataRepository } from 'packages/restBuilder/core/dataHandler'; class Repository extends DataRepository { - async passwordReset(userId, token, expiresAt, trx = null) { + async passwordResetCreateToken(userId, token, expiresAt, trx = null) { return await connection.users.update({ where: { id: userId, diff --git a/backend/src/core/modules/auth/service/auth.service.js b/backend/src/core/modules/auth/service/auth.service.js index d54625b..e241cd9 100644 --- a/backend/src/core/modules/auth/service/auth.service.js +++ b/backend/src/core/modules/auth/service/auth.service.js @@ -30,10 +30,6 @@ class Service { throw new DuplicateException("Email already in use"); } - if (registerDto.password !== registerDto.confirmPassword) { - throw new BadRequestException("Passwords do not match " + registerDto.password + ' ' + registerDto.confirmPassword); - } - let existingRole = await connection.roles.findFirst({ where: {name: registerDto.role} }); @@ -60,12 +56,15 @@ class Service { const accessToken = this.jwtService.sign(JwtPayload({ id: result.id, role: [registerDto.role] })); return { - id: result.id, - full_name: registerDto.fullName, - email: registerDto.email, - role_id: role_id, - access_token: accessToken, - refresh_token: refreshToken, + data: { + id: result.id, + full_name: registerDto.fullName, + email: registerDto.email, + role_id: role_id, + access_token: accessToken, + refresh_token: refreshToken, + }, + message: "User registered successfully", }; } @@ -101,14 +100,12 @@ class Service { const { token: refreshToken } = await this.#createRefreshToken(user.id); const accessToken = this.jwtService.sign(JwtPayload({ id: user.id, role: [loginDto.role] })); - const result = { - user: userData, - access_token: accessToken, - refresh_token: refreshToken, - }; - return { - data: result, + data: { + user: userData, + access_token: accessToken, + refresh_token: refreshToken, + }, message: "Login successful.", }; } @@ -123,8 +120,6 @@ class Service { const otp = crypto.randomInt(100000, 999999).toString(); const expiresAt = new Date(Date.now() + 15*60*1000); - console.log(new Date(Date.now() + 15*60*1000)); - await this.forgotPasswordRepository.forgotPassword(existingEmail.id, otp, expiresAt); return { @@ -160,6 +155,27 @@ class Service { }; } + async resetPassword(resetPasswordDto) { + const user = await connection.users.findFirst({ + where: { password_reset_token: resetPasswordDto.token }, + select: { id: true }, + }) + if (!user) { + throw new UnAuthorizedException("Invalid or expired token"); + } + + resetPasswordDto.newPassword = await this.bcryptService.hash(resetPasswordDto.newPassword); + + await connection.users.update({ + where: {id: user.id}, + data: {password_hash: resetPasswordDto.newPassword}, + }); + + return { + message: "Password has been reset successfully", + } + } + async #createRefreshToken(userId) { const token = crypto.randomBytes(32).toString('hex'); const expiresAt = new Date(Date.now() + REFRESH_TOKEN_EXPIRY); @@ -173,7 +189,7 @@ class Service { async #createPasswordResetToken(userId) { const token = crypto.randomBytes(32).toString('hex'); const expiresAt = new Date(Date.now() + PASSWORD_RESET_TOKEN_EXPIRY); - const record = await this.passwordResetRepository.passwordReset(userId, token, expiresAt); + const record = await this.passwordResetRepository.passwordResetCreateToken(userId, token, expiresAt); return { id: record.user_id || record, token: record.token || token From 4c2b9c7c85439e7f5ac038cef0e270f5d7fd167d Mon Sep 17 00:00:00 2001 From: DQGiac Date: Mon, 18 May 2026 15:42:32 +0700 Subject: [PATCH 08/26] feat[FI-12}: Added viewing and updating personal data. Changed formats of date and email for input --- backend/src/core/api/auth/auth.controller.js | 12 +++- backend/src/core/api/auth/auth.resolver.js | 19 +++++++ backend/src/core/api/params/get-my-profile.js | 8 +++ backend/src/core/api/params/index.js | 1 + backend/src/core/modules/auth/dto/index.js | 1 + .../modules/auth/dto/update-my-profile.dto.js | 18 ++++++ .../forgot-password.interceptor.js | 4 +- .../interceptor/get-my-profile.interceptor.js | 10 ++++ .../core/modules/auth/interceptor/index.js | 2 + .../interceptor/login-lawyer.interceptor.js | 4 +- .../interceptor/login-user.interceptor.js | 4 +- .../register-lawyer.interceptor.js | 6 +- .../interceptor/register-user.interceptor.js | 4 +- .../update-my-profile.interceptor.js | 13 +++++ .../interceptor/verify-otp.interceptor.js | 4 +- .../repository/refresh-token.repository.js | 11 +++- .../core/modules/auth/service/auth.service.js | 55 ++++++++++++++++--- .../interceptor/create-user.interceptor.js | 4 +- .../interceptor/update-user.interceptor.js | 4 +- 19 files changed, 164 insertions(+), 20 deletions(-) create mode 100644 backend/src/core/api/params/get-my-profile.js create mode 100644 backend/src/core/api/params/index.js create mode 100644 backend/src/core/modules/auth/dto/update-my-profile.dto.js create mode 100644 backend/src/core/modules/auth/interceptor/get-my-profile.interceptor.js create mode 100644 backend/src/core/modules/auth/interceptor/update-my-profile.interceptor.js diff --git a/backend/src/core/api/auth/auth.controller.js b/backend/src/core/api/auth/auth.controller.js index db64eea..f55ead1 100644 --- a/backend/src/core/api/auth/auth.controller.js +++ b/backend/src/core/api/auth/auth.controller.js @@ -1,5 +1,5 @@ import { AuthService } from '../../modules/auth/service/auth.service'; -import { RegisterUserDto, RegisterLawyerDto, LoginUserDto, LoginLawyerDto, ForgotPasswordDto, VerifyOtpDto, ResetPasswordDto } from '../../modules/auth'; +import { RegisterUserDto, RegisterLawyerDto, LoginUserDto, LoginLawyerDto, ForgotPasswordDto, VerifyOtpDto, ResetPasswordDto, UpdateMyProfileDto } from '../../modules/auth'; import { ValidHttpResponse } from '../../../packages/handler/response/validHttp.response'; class Controller { @@ -11,12 +11,20 @@ class Controller { const data = await this.service.register(RegisterUserDto(req.body)); return ValidHttpResponse.toOkResponse(data); } - registerLawyer = async req => { const data = await this.service.register(RegisterLawyerDto(req.body)); return ValidHttpResponse.toOkResponse(data); } + getMyProfile = async req => { + const data = await this.service.getMyProfile(req.user.payload.id); // nếu sử dụng req.user.payload.id thì không cần dùng tới phần nhập như kiểu swagger (nhập id như thế nào cũng sẽ chỉ hiện mỗi hồ sơ của họ). Như này được không ạ? (@c quynh) + return ValidHttpResponse.toOkResponse(data); + } + updateMyProfile = async req => { + const data = await this.service.updateMyProfile(UpdateMyProfileDto(req.body), req.user.payload.id); + return ValidHttpResponse.toOkResponse(data); + } + loginUser = async req => { const data = await this.service.login(LoginUserDto(req.body)); return ValidHttpResponse.toOkResponse(data); diff --git a/backend/src/core/api/auth/auth.resolver.js b/backend/src/core/api/auth/auth.resolver.js index 5dd91ca..65d76c9 100644 --- a/backend/src/core/api/auth/auth.resolver.js +++ b/backend/src/core/api/auth/auth.resolver.js @@ -3,12 +3,15 @@ import { LoginLawyerInterceptor, RegisterUserInterceptor, RegisterLawyerInterceptor, + GetMyProfileInterceptor, + UpdateMyProfileInterceptor, ForgotPasswordInterceptor, VerifyOtpInterceptor, ResetPasswordInterceptor, } from 'core/modules/auth'; import { Module } from 'packages/handler/Module'; import { AuthController } from './auth.controller'; +import { GetMyProfileParams } from '../params'; export const AuthResolver = Module.builder() .addPrefix({ @@ -45,6 +48,22 @@ export const AuthResolver = Module.builder() body: 'RegisterLawyerDto', controller: AuthController.registerLawyer, }, + { + route: '/me/:id', + method: 'get', + params: [GetMyProfileParams], + interceptors: [GetMyProfileInterceptor], + controller: AuthController.getMyProfile, + preAuthorization: true, + }, + { + route: '/update_my_profile', + method: 'post', + interceptors: [UpdateMyProfileInterceptor], + body: 'UpdateMyProfileDto', + controller: AuthController.updateMyProfile, + preAuthorization: true, + }, { route: '/forgot_password', method: 'post', diff --git a/backend/src/core/api/params/get-my-profile.js b/backend/src/core/api/params/get-my-profile.js new file mode 100644 index 0000000..0c1fc83 --- /dev/null +++ b/backend/src/core/api/params/get-my-profile.js @@ -0,0 +1,8 @@ +import { SwaggerDocument } from 'packages/swagger'; + +export const GetMyProfileParams = SwaggerDocument.ApiParams({ + name: 'id', + paramsIn: 'path', + type: 'string', + description: 'User UUID', +}); diff --git a/backend/src/core/api/params/index.js b/backend/src/core/api/params/index.js new file mode 100644 index 0000000..a34fd91 --- /dev/null +++ b/backend/src/core/api/params/index.js @@ -0,0 +1 @@ +export * from './get-my-profile.js' \ No newline at end of file diff --git a/backend/src/core/modules/auth/dto/index.js b/backend/src/core/modules/auth/dto/index.js index 1c101aa..80d77a9 100644 --- a/backend/src/core/modules/auth/dto/index.js +++ b/backend/src/core/modules/auth/dto/index.js @@ -6,3 +6,4 @@ export * from './jwt-sign.dto'; export * from './forgot-password.dto'; export * from './verify-otp.dto'; export * from './reset-password.dto'; +export * from './update-my-profile.dto'; diff --git a/backend/src/core/modules/auth/dto/update-my-profile.dto.js b/backend/src/core/modules/auth/dto/update-my-profile.dto.js new file mode 100644 index 0000000..c0b9430 --- /dev/null +++ b/backend/src/core/modules/auth/dto/update-my-profile.dto.js @@ -0,0 +1,18 @@ +import { ApiDocument } from 'core/config/swagger.config'; +import { SwaggerDocument } from 'packages/swagger'; + +ApiDocument.addModel('UpdateMyProfileDto', { + avatar_url: SwaggerDocument.ApiProperty({ type:'string' }), + full_name: SwaggerDocument.ApiProperty({ type:'string' }), + phone: SwaggerDocument.ApiProperty({ type:'string' }), + date_of_birth: SwaggerDocument.ApiProperty({ type:'string' }), + location: SwaggerDocument.ApiProperty({ type:'string' }), +}); + +export const UpdateMyProfileDto = body => ({ + avatar_url: body.avatar_url, + full_name: body.full_name, + phone: body.phone, + date_of_birth: body.date_of_birth, + location: body.location, +}); \ No newline at end of file diff --git a/backend/src/core/modules/auth/interceptor/forgot-password.interceptor.js b/backend/src/core/modules/auth/interceptor/forgot-password.interceptor.js index a4b0d75..d3ed39d 100644 --- a/backend/src/core/modules/auth/interceptor/forgot-password.interceptor.js +++ b/backend/src/core/modules/auth/interceptor/forgot-password.interceptor.js @@ -4,6 +4,8 @@ import Joi from 'joi'; export const ForgotPasswordInterceptor = new DefaultValidatorInterceptor( Joi.object({ - email: JoiUtils.email().required(), + email: JoiUtils.email().required().pattern(/^.*@.*\.(com|net|org)$/).message({ + 'string.pattern.base': 'Email must be a valid email address.' + }), }) ); diff --git a/backend/src/core/modules/auth/interceptor/get-my-profile.interceptor.js b/backend/src/core/modules/auth/interceptor/get-my-profile.interceptor.js new file mode 100644 index 0000000..0aeb59a --- /dev/null +++ b/backend/src/core/modules/auth/interceptor/get-my-profile.interceptor.js @@ -0,0 +1,10 @@ +import { DefaultValidatorInterceptor } from 'core/infrastructure/interceptor'; +import { JoiUtils } from 'core/utils'; +import Joi from 'joi'; + +export const GetMyProfileInterceptor = new DefaultValidatorInterceptor( + Joi.object({ + id: JoiUtils.uuid() + }), + 'params' +); diff --git a/backend/src/core/modules/auth/interceptor/index.js b/backend/src/core/modules/auth/interceptor/index.js index 87dae84..753e3bf 100644 --- a/backend/src/core/modules/auth/interceptor/index.js +++ b/backend/src/core/modules/auth/interceptor/index.js @@ -2,6 +2,8 @@ export * from './login-user.interceptor'; export * from './login-lawyer.interceptor'; export * from './register-lawyer.interceptor'; export * from './register-user.interceptor'; +export * from './get-my-profile.interceptor'; +export * from './update-my-profile.interceptor'; export * from './forgot-password.interceptor'; export * from './verify-otp.interceptor'; export * from './reset-password.interceptor'; diff --git a/backend/src/core/modules/auth/interceptor/login-lawyer.interceptor.js b/backend/src/core/modules/auth/interceptor/login-lawyer.interceptor.js index e3a1882..0aa4ba9 100644 --- a/backend/src/core/modules/auth/interceptor/login-lawyer.interceptor.js +++ b/backend/src/core/modules/auth/interceptor/login-lawyer.interceptor.js @@ -4,7 +4,9 @@ import Joi from 'joi'; export const LoginLawyerInterceptor = new DefaultValidatorInterceptor( Joi.object({ - email: JoiUtils.email().required(), + email: JoiUtils.email().required().pattern(/^.*@.*\.(com|net|org)$/).message({ + 'string.pattern.base': 'Email must be a valid email address.' + }), password: JoiUtils.password().required(), licenseNumber: JoiUtils.requiredString(), }) diff --git a/backend/src/core/modules/auth/interceptor/login-user.interceptor.js b/backend/src/core/modules/auth/interceptor/login-user.interceptor.js index 6052bdd..6a8917a 100644 --- a/backend/src/core/modules/auth/interceptor/login-user.interceptor.js +++ b/backend/src/core/modules/auth/interceptor/login-user.interceptor.js @@ -4,7 +4,9 @@ import Joi from 'joi'; export const LoginUserInterceptor = new DefaultValidatorInterceptor( Joi.object({ - email: JoiUtils.email().required(), + email: JoiUtils.email().required().pattern(/^.*@.*\.(com|net|org)$/).message({ + 'string.pattern.base': 'Email must be a valid email address.' + }), password: JoiUtils.password().required(), }) ); diff --git a/backend/src/core/modules/auth/interceptor/register-lawyer.interceptor.js b/backend/src/core/modules/auth/interceptor/register-lawyer.interceptor.js index 5120178..e8db0fa 100644 --- a/backend/src/core/modules/auth/interceptor/register-lawyer.interceptor.js +++ b/backend/src/core/modules/auth/interceptor/register-lawyer.interceptor.js @@ -6,7 +6,9 @@ export const RegisterLawyerInterceptor = new DefaultValidatorInterceptor( Joi.object({ role: JoiUtils.requiredString(), fullName: JoiUtils.requiredString(), - email: JoiUtils.email().required(), + email: JoiUtils.email().required().pattern(/^.*@.*\.(com|net|org)$/).message({ + 'string.pattern.base': 'Email must be a valid email address.' + }), password: JoiUtils.password().min(8).pattern(/^(?=.*[a-zA-Z])(?=.*\d).{8,}$/).required().messages({ 'string.min': 'Password must be at least 8 characters', 'string.pattern.base': 'Password must contain at least one letter and one number', @@ -16,7 +18,7 @@ export const RegisterLawyerInterceptor = new DefaultValidatorInterceptor( }), licenseNumber: JoiUtils.requiredString(), licenseIssuer: JoiUtils.requiredString(), - licenseIssueDate: JoiUtils.requiredString().isoDate(), // e khong ro cai format date + licenseIssueDate: JoiUtils.requiredString().pattern(/^\d{4}-\d{2}-\d{2}$/), licenseFile: JoiUtils.requiredString(), referralCode: JoiUtils.optionalString().allow(''), }) diff --git a/backend/src/core/modules/auth/interceptor/register-user.interceptor.js b/backend/src/core/modules/auth/interceptor/register-user.interceptor.js index 30f49ea..cdb2b02 100644 --- a/backend/src/core/modules/auth/interceptor/register-user.interceptor.js +++ b/backend/src/core/modules/auth/interceptor/register-user.interceptor.js @@ -6,7 +6,9 @@ export const RegisterUserInterceptor = new DefaultValidatorInterceptor( Joi.object({ role: JoiUtils.requiredString(), fullName: JoiUtils.requiredString(), - email: JoiUtils.email().required(), + email: JoiUtils.email().required().pattern(/^.*@.*\.(com|net|org)$/).message({ + 'string.pattern.base': 'Email must be a valid email address.' + }), password: JoiUtils.password().min(8).pattern(/^(?=.*[a-zA-Z])(?=.*\d).{8,}$/).required().messages({ 'string.min': 'Password must be at least 8 characters', 'string.pattern.base': 'Password must contain at least one letter and one number', diff --git a/backend/src/core/modules/auth/interceptor/update-my-profile.interceptor.js b/backend/src/core/modules/auth/interceptor/update-my-profile.interceptor.js new file mode 100644 index 0000000..fb43108 --- /dev/null +++ b/backend/src/core/modules/auth/interceptor/update-my-profile.interceptor.js @@ -0,0 +1,13 @@ +import { DefaultValidatorInterceptor } from 'core/infrastructure/interceptor'; +import { JoiUtils } from 'core/utils'; +import Joi from 'joi'; + +export const UpdateMyProfileInterceptor = new DefaultValidatorInterceptor( + Joi.object({ + avatar_url: JoiUtils.optionalString().allow(''), + full_name: JoiUtils.optionalString().allow(''), + phone: JoiUtils.optionalString().allow(''), + date_of_birth: JoiUtils.optionalString().allow('').pattern(/^\d{4}-\d{2}-\d{2}$/), + location: JoiUtils.optionalString().allow(''), + }) +); \ No newline at end of file diff --git a/backend/src/core/modules/auth/interceptor/verify-otp.interceptor.js b/backend/src/core/modules/auth/interceptor/verify-otp.interceptor.js index dc02809..de5506f 100644 --- a/backend/src/core/modules/auth/interceptor/verify-otp.interceptor.js +++ b/backend/src/core/modules/auth/interceptor/verify-otp.interceptor.js @@ -4,7 +4,9 @@ import Joi from 'joi'; export const VerifyOtpInterceptor = new DefaultValidatorInterceptor( Joi.object({ - email: JoiUtils.email().required(), + email: JoiUtils.email().required().pattern(/^.*@.*\.(com|net|org)$/).message({ + 'string.pattern.base': 'Email must be a valid email address.' + }), otp: JoiUtils.requiredString(), }) ); \ No newline at end of file diff --git a/backend/src/core/modules/auth/repository/refresh-token.repository.js b/backend/src/core/modules/auth/repository/refresh-token.repository.js index b389fc6..8db8b9f 100644 --- a/backend/src/core/modules/auth/repository/refresh-token.repository.js +++ b/backend/src/core/modules/auth/repository/refresh-token.repository.js @@ -3,8 +3,15 @@ import connection from 'core/database'; class Repository extends DataRepository { async createToken(userId, token, expiresAt, trx = null) { - return await connection.refresh_tokens.create({ - data: { + return await connection.refresh_tokens.upsert({ + where: { + user_id: userId, + }, + update: { + token, + expires_at: expiresAt, + }, + create: { user_id: userId, token, expires_at: expiresAt, diff --git a/backend/src/core/modules/auth/service/auth.service.js b/backend/src/core/modules/auth/service/auth.service.js index e241cd9..a4b9aae 100644 --- a/backend/src/core/modules/auth/service/auth.service.js +++ b/backend/src/core/modules/auth/service/auth.service.js @@ -1,6 +1,6 @@ import crypto from 'crypto'; import connection from 'core/database'; -import { pick } from 'lodash'; +import { pick, update } from 'lodash'; import { JwtPayload } from 'core/modules/auth/dto/jwt-sign.dto'; import { UserDataService } from 'core/modules/user/services/userData.service'; import { joinUserRoles } from 'core/utils/userFilter'; @@ -91,12 +91,14 @@ class Service { } const userData = { - id: user.id, + user_id: user.id, email: user.email, fullName: user.full_name, role: loginDto.role, }; + console.log(user.id); + const { token: refreshToken } = await this.#createRefreshToken(user.id); const accessToken = this.jwtService.sign(JwtPayload({ id: user.id, role: [loginDto.role] })); @@ -118,7 +120,7 @@ class Service { } const otp = crypto.randomInt(100000, 999999).toString(); - const expiresAt = new Date(Date.now() + 15*60*1000); + const expiresAt = new Date(Date.now() + FORGOT_PASSWORD_TOKEN_EXPIRY); await this.forgotPasswordRepository.forgotPassword(existingEmail.id, otp, expiresAt); @@ -138,8 +140,8 @@ class Service { select: { password_reset_token: true, password_reset_expiry: true } }); - if (existingOtp.password_reset_expiry < new Date(Date.now())) { - throw new UnAuthorizedException("OTP has expired " + (Date.now()).toString()); + if (existingOtp.password_reset_expiry < Date.now()) { + throw new UnAuthorizedException("OTP has expired " + existingOtp.password_reset_expiry); } if (existingOtp.password_reset_token != verifyOtpDto.otp) { @@ -167,8 +169,9 @@ class Service { resetPasswordDto.newPassword = await this.bcryptService.hash(resetPasswordDto.newPassword); await connection.users.update({ - where: {id: user.id}, - data: {password_hash: resetPasswordDto.newPassword}, + where: { id: user.id }, + data: { password_hash: resetPasswordDto.newPassword }, + select: { id: true }, }); return { @@ -176,6 +179,44 @@ class Service { } } + async getMyProfile(userId) { + return await connection.users.findFirst({ + where: { id: userId }, + select: { id: true, avatar_url: true, full_name: true, email: true, phone: true, date_of_birth: true, location: true } + }); + } + + async updateMyProfile(updateMyProfileDto, userId) { + const result = await connection.users.update({ + where: { + id: userId, + }, + data: { + avatar_url: updateMyProfileDto.avatar_url, + full_name: updateMyProfileDto.full_name, + phone: updateMyProfileDto.phone, + date_of_birth: new Date(updateMyProfileDto.date_of_birth).toISOString(), + location: updateMyProfileDto.location, + }, + select: { + id: true, + avatar_url: true, + full_name: true, + email: true, + phone: true, + date_of_birth: true, + location: true, + subscriptions: { + select: { plan_name: true } + }, + created_at: true, + } + }); + return { + data: result, + } + } + async #createRefreshToken(userId) { const token = crypto.randomBytes(32).toString('hex'); const expiresAt = new Date(Date.now() + REFRESH_TOKEN_EXPIRY); diff --git a/backend/src/core/modules/user/interceptor/create-user.interceptor.js b/backend/src/core/modules/user/interceptor/create-user.interceptor.js index 484e376..581f949 100644 --- a/backend/src/core/modules/user/interceptor/create-user.interceptor.js +++ b/backend/src/core/modules/user/interceptor/create-user.interceptor.js @@ -4,7 +4,9 @@ import { JoiUtils } from '../../../utils'; export const CreateUserInterceptor = new DefaultValidatorInterceptor( Joi.object({ - email: JoiUtils.email().required(), + email: JoiUtils.email().required().pattern(/^.*@.*\.(com|net|org)$/).message({ + 'string.pattern.base': 'Email must be a valid email address.' + }), fullName: JoiUtils.requiredString().min(1), password: JoiUtils.password().required(), confirmPassword: JoiUtils.password().required(), diff --git a/backend/src/core/modules/user/interceptor/update-user.interceptor.js b/backend/src/core/modules/user/interceptor/update-user.interceptor.js index d3bacfd..c74febe 100644 --- a/backend/src/core/modules/user/interceptor/update-user.interceptor.js +++ b/backend/src/core/modules/user/interceptor/update-user.interceptor.js @@ -4,7 +4,9 @@ import { JoiUtils } from '../../../utils'; export const UpdateUserInterceptor = new DefaultValidatorInterceptor( Joi.object({ - email: JoiUtils.email().required(), + email: JoiUtils.email().required().pattern(/^.*@.*\.(com|net|org)$/).message({ + 'string.pattern.base': 'Email must be a valid email address.' + }), fullName: JoiUtils.requiredString().min(1), }) ); From 01a0f787d222244d65f1a4895f9041bbd356b197 Mon Sep 17 00:00:00 2001 From: DQGiac Date: Mon, 18 May 2026 17:38:04 +0700 Subject: [PATCH 09/26] feat[FI-12]: Added refresh-token. Renamed confusing functions. --- backend/src/core/api/auth/auth.controller.js | 7 ++- backend/src/core/api/auth/auth.resolver.js | 9 ++++ backend/src/core/modules/auth/dto/index.js | 1 + .../modules/auth/dto/refresh-token.dto.js | 10 ++++ .../core/modules/auth/interceptor/index.js | 1 + .../interceptor/refresh-token.interceptor.js | 9 ++++ ....js => create-refresh-token.repository.js} | 2 +- .../src/core/modules/auth/repository/index.js | 2 +- .../core/modules/auth/service/auth.service.js | 51 +++++++++++++++++-- 9 files changed, 84 insertions(+), 8 deletions(-) create mode 100644 backend/src/core/modules/auth/dto/refresh-token.dto.js create mode 100644 backend/src/core/modules/auth/interceptor/refresh-token.interceptor.js rename backend/src/core/modules/auth/repository/{refresh-token.repository.js => create-refresh-token.repository.js} (89%) diff --git a/backend/src/core/api/auth/auth.controller.js b/backend/src/core/api/auth/auth.controller.js index f55ead1..a49d4e5 100644 --- a/backend/src/core/api/auth/auth.controller.js +++ b/backend/src/core/api/auth/auth.controller.js @@ -1,5 +1,5 @@ import { AuthService } from '../../modules/auth/service/auth.service'; -import { RegisterUserDto, RegisterLawyerDto, LoginUserDto, LoginLawyerDto, ForgotPasswordDto, VerifyOtpDto, ResetPasswordDto, UpdateMyProfileDto } from '../../modules/auth'; +import { RegisterUserDto, RegisterLawyerDto, LoginUserDto, LoginLawyerDto, ForgotPasswordDto, VerifyOtpDto, ResetPasswordDto, UpdateMyProfileDto, RefreshTokenDto } from '../../modules/auth'; import { ValidHttpResponse } from '../../../packages/handler/response/validHttp.response'; class Controller { @@ -46,6 +46,11 @@ class Controller { const data = await this.service.resetPassword(ResetPasswordDto(req.body)); return ValidHttpResponse.toOkResponse(data); } + + refreshToken = async req => { + const data = await this.service.refreshToken(RefreshTokenDto(req.body), req.user.payload.id); + return ValidHttpResponse.toOkResponse(data); + } } export const AuthController = new Controller(); diff --git a/backend/src/core/api/auth/auth.resolver.js b/backend/src/core/api/auth/auth.resolver.js index 65d76c9..c9aa1cf 100644 --- a/backend/src/core/api/auth/auth.resolver.js +++ b/backend/src/core/api/auth/auth.resolver.js @@ -8,6 +8,7 @@ import { ForgotPasswordInterceptor, VerifyOtpInterceptor, ResetPasswordInterceptor, + RefreshTokenInterceptor, } from 'core/modules/auth'; import { Module } from 'packages/handler/Module'; import { AuthController } from './auth.controller'; @@ -85,4 +86,12 @@ export const AuthResolver = Module.builder() body: 'ResetPasswordDto', controller: AuthController.resetPassword, }, + { + route: '/refresh-token', + method: 'post', + interceptors: [RefreshTokenInterceptor], + body: 'RefreshTokenDto', + controller: AuthController.refreshToken, + preAuthorization: true, + }, ]); diff --git a/backend/src/core/modules/auth/dto/index.js b/backend/src/core/modules/auth/dto/index.js index 80d77a9..631b862 100644 --- a/backend/src/core/modules/auth/dto/index.js +++ b/backend/src/core/modules/auth/dto/index.js @@ -7,3 +7,4 @@ export * from './forgot-password.dto'; export * from './verify-otp.dto'; export * from './reset-password.dto'; export * from './update-my-profile.dto'; +export * from './refresh-token.dto'; diff --git a/backend/src/core/modules/auth/dto/refresh-token.dto.js b/backend/src/core/modules/auth/dto/refresh-token.dto.js new file mode 100644 index 0000000..3ef5c33 --- /dev/null +++ b/backend/src/core/modules/auth/dto/refresh-token.dto.js @@ -0,0 +1,10 @@ +import { ApiDocument } from 'core/config/swagger.config'; +import { SwaggerDocument } from 'packages/swagger'; + +ApiDocument.addModel('RefreshTokenDto', { + refresh_token: SwaggerDocument.ApiProperty({ type: 'string' }), +}); + +export const RefreshTokenDto = body => ({ + refresh_token: body.refresh_token, +}); \ No newline at end of file diff --git a/backend/src/core/modules/auth/interceptor/index.js b/backend/src/core/modules/auth/interceptor/index.js index 753e3bf..47ab75a 100644 --- a/backend/src/core/modules/auth/interceptor/index.js +++ b/backend/src/core/modules/auth/interceptor/index.js @@ -7,3 +7,4 @@ export * from './update-my-profile.interceptor'; export * from './forgot-password.interceptor'; export * from './verify-otp.interceptor'; export * from './reset-password.interceptor'; +export * from './refresh-token.interceptor'; diff --git a/backend/src/core/modules/auth/interceptor/refresh-token.interceptor.js b/backend/src/core/modules/auth/interceptor/refresh-token.interceptor.js new file mode 100644 index 0000000..71ac046 --- /dev/null +++ b/backend/src/core/modules/auth/interceptor/refresh-token.interceptor.js @@ -0,0 +1,9 @@ +import { DefaultValidatorInterceptor } from 'core/infrastructure/interceptor'; +import { JoiUtils } from 'core/utils'; +import Joi from 'joi'; + +export const RefreshTokenInterceptor = new DefaultValidatorInterceptor( + Joi.object({ + refresh_token: JoiUtils.requiredString(), + }) +); diff --git a/backend/src/core/modules/auth/repository/refresh-token.repository.js b/backend/src/core/modules/auth/repository/create-refresh-token.repository.js similarity index 89% rename from backend/src/core/modules/auth/repository/refresh-token.repository.js rename to backend/src/core/modules/auth/repository/create-refresh-token.repository.js index 8db8b9f..c5d30d9 100644 --- a/backend/src/core/modules/auth/repository/refresh-token.repository.js +++ b/backend/src/core/modules/auth/repository/create-refresh-token.repository.js @@ -21,4 +21,4 @@ class Repository extends DataRepository { } } -export const RefreshTokenRepository = new Repository('refresh_tokens'); \ No newline at end of file +export const CreateRefreshTokenRepository = new Repository('refresh_tokens'); \ No newline at end of file diff --git a/backend/src/core/modules/auth/repository/index.js b/backend/src/core/modules/auth/repository/index.js index efefad1..e16f863 100644 --- a/backend/src/core/modules/auth/repository/index.js +++ b/backend/src/core/modules/auth/repository/index.js @@ -1,3 +1,3 @@ -export * from './refresh-token.repository'; +export * from './create-refresh-token.repository'; export * from './forgot-password.repository'; export * from './password-reset.repository'; \ No newline at end of file diff --git a/backend/src/core/modules/auth/service/auth.service.js b/backend/src/core/modules/auth/service/auth.service.js index a4b9aae..1b33283 100644 --- a/backend/src/core/modules/auth/service/auth.service.js +++ b/backend/src/core/modules/auth/service/auth.service.js @@ -8,7 +8,8 @@ import { BcryptService } from './bcrypt.service'; import { JwtService } from './jwt.service'; import { UserRepository } from '../../user/user.repository'; import { UnAuthorizedException, DuplicateException, BadRequestException } from '../../../../packages/httpException'; -import { RefreshTokenRepository, PasswordResetRepository, ForgotPasswordRepository } from '../repository'; +import { CreateRefreshTokenRepository, PasswordResetRepository, ForgotPasswordRepository } from '../repository'; +import { re } from 'prettier'; const REFRESH_TOKEN_EXPIRY = 24 * 60 * 60 * 1000; // 1 day const FORGOT_PASSWORD_TOKEN_EXPIRY = 15 * 60 * 1000; // 15 minute @@ -19,7 +20,7 @@ class Service { this.userRepository = UserRepository; this.jwtService = JwtService; this.bcryptService = BcryptService; - this.refreshTokenRepository = RefreshTokenRepository; + this.createRefreshTokenRepository = CreateRefreshTokenRepository; this.passwordResetRepository = PasswordResetRepository; this.forgotPasswordRepository = ForgotPasswordRepository; } @@ -97,8 +98,6 @@ class Service { role: loginDto.role, }; - console.log(user.id); - const { token: refreshToken } = await this.#createRefreshToken(user.id); const accessToken = this.jwtService.sign(JwtPayload({ id: user.id, role: [loginDto.role] })); @@ -217,10 +216,52 @@ class Service { } } + async refreshToken(refreshTokenDto, userId) { + const result = await connection.refresh_tokens.findFirst({ + where: { token: refreshTokenDto.refresh_token }, + select: { expires_at: true } + }); + if (!result) { + throw new UnAuthorizedException("Invalid or expired refresh token"); + } + if (result.expires_at < Date.now()) { + throw new UnAuthorizedException("Invalid or expired refresh token"); + } + + const { token: refreshToken } = await this.#createRefreshToken(userId); + + await connection.refresh_tokens.update({ + where: {user_id: userId}, + data: { + token: refreshTokenDto.refresh_token, + expires_at: new Date(Date.now() + REFRESH_TOKEN_EXPIRY) + }, + select: { token: true } + }); + + const {userRole} = await connection.users.findFirst({ + where: {id: userId}, + select: { + roles: { + select: { name: true } + }, + } + }) + + const accessToken = this.jwtService.sign(JwtPayload({ id: userId, role: [userRole] })); + + return { + data: { + accessToken: accessToken, + refreshToken: refreshToken, + }, + } + } + async #createRefreshToken(userId) { const token = crypto.randomBytes(32).toString('hex'); const expiresAt = new Date(Date.now() + REFRESH_TOKEN_EXPIRY); - const record = await this.refreshTokenRepository.createToken(userId, token, expiresAt); + const record = await this.createRefreshTokenRepository.createToken(userId, token, expiresAt); return { id: record.user_id || record, token: record.token || token From 6aef145f3e2e9b92c8c4d7b4c9e8ff37c3623a65 Mon Sep 17 00:00:00 2001 From: DQGiac Date: Wed, 20 May 2026 10:09:15 +0700 Subject: [PATCH 10/26] feat[FI-12]: Implemented RBAC middleware and auth guard. Cleaned up code. --- backend/src/core/api/auth/auth.controller.js | 8 ++- backend/src/core/api/auth/auth.resolver.js | 15 ++++ .../core/modules/auth/dto/login-lawyer.dto.js | 12 ++-- .../core/modules/auth/dto/login-user.dto.js | 12 ++-- .../modules/auth/dto/register-lawyer.dto.js | 4 +- .../modules/auth/dto/register-user.dto.js | 4 +- backend/src/core/modules/auth/index.js | 1 - .../register-lawyer.interceptor.js | 2 +- .../interceptor/register-user.interceptor.js | 2 +- .../auth/middleware/auth.middleware.js | 17 +++++ .../src/core/modules/auth/middleware/index.js | 2 + .../auth/middleware/rbac.middleware.js | 10 +++ .../create-refresh-token.repository.js | 24 ------- .../repository/forgot-password.repository.js | 19 ----- .../src/core/modules/auth/repository/index.js | 3 - .../repository/password-reset.repository.js | 19 ----- .../core/modules/auth/service/auth.service.js | 70 +++++++++++++++---- 17 files changed, 126 insertions(+), 98 deletions(-) create mode 100644 backend/src/core/modules/auth/middleware/auth.middleware.js create mode 100644 backend/src/core/modules/auth/middleware/index.js create mode 100644 backend/src/core/modules/auth/middleware/rbac.middleware.js delete mode 100644 backend/src/core/modules/auth/repository/create-refresh-token.repository.js delete mode 100644 backend/src/core/modules/auth/repository/forgot-password.repository.js delete mode 100644 backend/src/core/modules/auth/repository/index.js delete mode 100644 backend/src/core/modules/auth/repository/password-reset.repository.js diff --git a/backend/src/core/api/auth/auth.controller.js b/backend/src/core/api/auth/auth.controller.js index a49d4e5..13f903c 100644 --- a/backend/src/core/api/auth/auth.controller.js +++ b/backend/src/core/api/auth/auth.controller.js @@ -1,6 +1,7 @@ import { AuthService } from '../../modules/auth/service/auth.service'; import { RegisterUserDto, RegisterLawyerDto, LoginUserDto, LoginLawyerDto, ForgotPasswordDto, VerifyOtpDto, ResetPasswordDto, UpdateMyProfileDto, RefreshTokenDto } from '../../modules/auth'; import { ValidHttpResponse } from '../../../packages/handler/response/validHttp.response'; +import { valid } from 'joi'; class Controller { constructor() { @@ -17,7 +18,7 @@ class Controller { } getMyProfile = async req => { - const data = await this.service.getMyProfile(req.user.payload.id); // nếu sử dụng req.user.payload.id thì không cần dùng tới phần nhập như kiểu swagger (nhập id như thế nào cũng sẽ chỉ hiện mỗi hồ sơ của họ). Như này được không ạ? (@c quynh) + const data = await this.service.getMyProfile(req.user.payload.id); // nếu sử dụng req.user.payload.id thì không cần dùng tới phần nhập như kiểu swagger (nhập id như thế nào (mặc dù cũng cần phải đúng với format của uuid) cũng sẽ chỉ hiện mỗi hồ sơ của họ --> bảo mật hơn?). Như này được không ạ? (@c quynh) return ValidHttpResponse.toOkResponse(data); } updateMyProfile = async req => { @@ -51,6 +52,11 @@ class Controller { const data = await this.service.refreshToken(RefreshTokenDto(req.body), req.user.payload.id); return ValidHttpResponse.toOkResponse(data); } + + uploadAvatar = async req => { + const data = await this.service.uploadAvatar(UploadAvatarDto(req.files)); + return ValidHttpResponse.toOkResponse(data); + } } export const AuthController = new Controller(); diff --git a/backend/src/core/api/auth/auth.resolver.js b/backend/src/core/api/auth/auth.resolver.js index c9aa1cf..95da09a 100644 --- a/backend/src/core/api/auth/auth.resolver.js +++ b/backend/src/core/api/auth/auth.resolver.js @@ -13,6 +13,9 @@ import { import { Module } from 'packages/handler/Module'; import { AuthController } from './auth.controller'; import { GetMyProfileParams } from '../params'; +import { authMiddleware } from 'core/modules/auth/middleware/auth.middleware'; +import { uploadMediaSwagger } from 'core/common/swagger'; +import { MediaInterceptor } from 'core/modules/document'; export const AuthResolver = Module.builder() .addPrefix({ @@ -54,6 +57,7 @@ export const AuthResolver = Module.builder() method: 'get', params: [GetMyProfileParams], interceptors: [GetMyProfileInterceptor], + middleware: [authMiddleware], // phần này em không rõ lắm (@c quynh) controller: AuthController.getMyProfile, preAuthorization: true, }, @@ -61,6 +65,7 @@ export const AuthResolver = Module.builder() route: '/update_my_profile', method: 'post', interceptors: [UpdateMyProfileInterceptor], + middleware: [authMiddleware], // tương tự như trên (@c quynh) body: 'UpdateMyProfileDto', controller: AuthController.updateMyProfile, preAuthorization: true, @@ -90,8 +95,18 @@ export const AuthResolver = Module.builder() route: '/refresh-token', method: 'post', interceptors: [RefreshTokenInterceptor], + middleware: [authMiddleware], // tương tự như trên (@c quynh) body: 'RefreshTokenDto', controller: AuthController.refreshToken, preAuthorization: true, }, + { + route: '/avatar', + method: 'post', + params: [uploadMediaSwagger], + consumes: ['multipart/form-data'], + interceptors: [new MediaInterceptor(10)], + controller: AuthController.uploadAvatar, + preAuthorization: true + }, ]); diff --git a/backend/src/core/modules/auth/dto/login-lawyer.dto.js b/backend/src/core/modules/auth/dto/login-lawyer.dto.js index 7c80477..76b3557 100644 --- a/backend/src/core/modules/auth/dto/login-lawyer.dto.js +++ b/backend/src/core/modules/auth/dto/login-lawyer.dto.js @@ -1,15 +1,15 @@ import { ApiDocument } from 'core/config/swagger.config'; import { SwaggerDocument } from 'packages/swagger'; -ApiDocument.addModel('LoginLawyerDto', - { - email: SwaggerDocument.ApiProperty({ type: 'string' }), - password: SwaggerDocument.ApiProperty({ type: 'string' }), - licenseNumber: SwaggerDocument.ApiProperty({ type: 'string' }) - }); +ApiDocument.addModel('LoginLawyerDto', { + email: SwaggerDocument.ApiProperty({ type: 'string' }), + password: SwaggerDocument.ApiProperty({ type: 'string' }), + licenseNumber: SwaggerDocument.ApiProperty({ type: 'string' }), +}); export const LoginLawyerDto = body => ({ email: body.email, password: body.password, licenseNumber: body.licenseNumber, + role: "LAWYER", }); diff --git a/backend/src/core/modules/auth/dto/login-user.dto.js b/backend/src/core/modules/auth/dto/login-user.dto.js index ddfe475..46cd37a 100644 --- a/backend/src/core/modules/auth/dto/login-user.dto.js +++ b/backend/src/core/modules/auth/dto/login-user.dto.js @@ -1,13 +1,13 @@ import { ApiDocument } from 'core/config/swagger.config'; import { SwaggerDocument } from 'packages/swagger'; -ApiDocument.addModel('LoginUserDto', - { - email: SwaggerDocument.ApiProperty({ type: 'string' }), - password: SwaggerDocument.ApiProperty({ type: 'string' }) - }); +ApiDocument.addModel('LoginUserDto', { + email: SwaggerDocument.ApiProperty({ type: 'string' }), + password: SwaggerDocument.ApiProperty({ type: 'string' }) +}); export const LoginUserDto = body => ({ email: body.email, - password: body.password + password: body.password, + role: "USER" }); diff --git a/backend/src/core/modules/auth/dto/register-lawyer.dto.js b/backend/src/core/modules/auth/dto/register-lawyer.dto.js index a49b52b..22e3d03 100644 --- a/backend/src/core/modules/auth/dto/register-lawyer.dto.js +++ b/backend/src/core/modules/auth/dto/register-lawyer.dto.js @@ -2,7 +2,7 @@ import { ApiDocument } from "core/config/swagger.config"; import { SwaggerDocument } from "packages/swagger"; ApiDocument.addModel('RegisterLawyerDto', { - role: SwaggerDocument.ApiProperty({type: 'string'}), + // role: SwaggerDocument.ApiProperty({type: 'string'}), fullName: SwaggerDocument.ApiProperty({type: 'string'}), email: SwaggerDocument.ApiProperty({type: 'string'}), password: SwaggerDocument.ApiProperty({type: 'string'}), @@ -15,7 +15,7 @@ ApiDocument.addModel('RegisterLawyerDto', { }); export const RegisterLawyerDto = body => ({ - role: body.role, + role: "LAWYER", fullName: body.fullName, email: body.email, password: body.password, diff --git a/backend/src/core/modules/auth/dto/register-user.dto.js b/backend/src/core/modules/auth/dto/register-user.dto.js index 3bcb7c1..3c25b8d 100644 --- a/backend/src/core/modules/auth/dto/register-user.dto.js +++ b/backend/src/core/modules/auth/dto/register-user.dto.js @@ -2,7 +2,7 @@ import { ApiDocument } from 'core/config/swagger.config'; import { SwaggerDocument } from 'packages/swagger'; ApiDocument.addModel('RegisterUserDto', { - role: SwaggerDocument.ApiProperty({ type: 'string' }), + // role: SwaggerDocument.ApiProperty({ type: 'string' }), fullName: SwaggerDocument.ApiProperty({type: 'string'}), email: SwaggerDocument.ApiProperty({ type: 'string' }), password: SwaggerDocument.ApiProperty({ type: 'string' }), @@ -10,7 +10,7 @@ ApiDocument.addModel('RegisterUserDto', { }); export const RegisterUserDto = body => ({ - role: body.role, + role: "USER", fullName: body.fullName, email: body.email, password: body.password, diff --git a/backend/src/core/modules/auth/index.js b/backend/src/core/modules/auth/index.js index 028de5f..646f1aa 100644 --- a/backend/src/core/modules/auth/index.js +++ b/backend/src/core/modules/auth/index.js @@ -1,4 +1,3 @@ export * from './dto'; export * from './service'; export * from './interceptor'; -export * from './repository'; \ No newline at end of file diff --git a/backend/src/core/modules/auth/interceptor/register-lawyer.interceptor.js b/backend/src/core/modules/auth/interceptor/register-lawyer.interceptor.js index e8db0fa..245ae29 100644 --- a/backend/src/core/modules/auth/interceptor/register-lawyer.interceptor.js +++ b/backend/src/core/modules/auth/interceptor/register-lawyer.interceptor.js @@ -4,7 +4,7 @@ import Joi from 'joi'; export const RegisterLawyerInterceptor = new DefaultValidatorInterceptor( Joi.object({ - role: JoiUtils.requiredString(), + // role: JoiUtils.requiredString(), fullName: JoiUtils.requiredString(), email: JoiUtils.email().required().pattern(/^.*@.*\.(com|net|org)$/).message({ 'string.pattern.base': 'Email must be a valid email address.' diff --git a/backend/src/core/modules/auth/interceptor/register-user.interceptor.js b/backend/src/core/modules/auth/interceptor/register-user.interceptor.js index cdb2b02..5853295 100644 --- a/backend/src/core/modules/auth/interceptor/register-user.interceptor.js +++ b/backend/src/core/modules/auth/interceptor/register-user.interceptor.js @@ -4,7 +4,7 @@ import Joi from 'joi'; export const RegisterUserInterceptor = new DefaultValidatorInterceptor( Joi.object({ - role: JoiUtils.requiredString(), + // role: JoiUtils.requiredString(), fullName: JoiUtils.requiredString(), email: JoiUtils.email().required().pattern(/^.*@.*\.(com|net|org)$/).message({ 'string.pattern.base': 'Email must be a valid email address.' diff --git a/backend/src/core/modules/auth/middleware/auth.middleware.js b/backend/src/core/modules/auth/middleware/auth.middleware.js new file mode 100644 index 0000000..1fe9816 --- /dev/null +++ b/backend/src/core/modules/auth/middleware/auth.middleware.js @@ -0,0 +1,17 @@ +import { getUserContext } from 'packages/authModel/module/user'; +import { UnAuthorizedException } from 'packages/httpException'; + +export const authMiddleware = (req) => { + try { + const user = getUserContext(req); + + if (!user) { + throw new UnAuthorizedException("Unauthorized or insufficient permissions"); + } + + req.user = user; + return true; + } catch (error) { + throw new UnAuthorizedException(error.message); + } +}; \ No newline at end of file diff --git a/backend/src/core/modules/auth/middleware/index.js b/backend/src/core/modules/auth/middleware/index.js new file mode 100644 index 0000000..df56f8a --- /dev/null +++ b/backend/src/core/modules/auth/middleware/index.js @@ -0,0 +1,2 @@ +export * from './auth.middleware' +export * from './rbac.middleware' \ No newline at end of file diff --git a/backend/src/core/modules/auth/middleware/rbac.middleware.js b/backend/src/core/modules/auth/middleware/rbac.middleware.js new file mode 100644 index 0000000..810b07e --- /dev/null +++ b/backend/src/core/modules/auth/middleware/rbac.middleware.js @@ -0,0 +1,10 @@ +import { UnAuthorizedException } from "packages/httpException"; + +export const rbac = (guard) => { + return (req) => { + if (!guard.canActive(req)) { + throw new UnAuthorizedException("Unauthorized or insufficient permissions"); + } + return true; + }; +}; \ No newline at end of file diff --git a/backend/src/core/modules/auth/repository/create-refresh-token.repository.js b/backend/src/core/modules/auth/repository/create-refresh-token.repository.js deleted file mode 100644 index c5d30d9..0000000 --- a/backend/src/core/modules/auth/repository/create-refresh-token.repository.js +++ /dev/null @@ -1,24 +0,0 @@ -import { DataRepository } from 'packages/restBuilder/core/dataHandler/data.repository'; -import connection from 'core/database'; - -class Repository extends DataRepository { - async createToken(userId, token, expiresAt, trx = null) { - return await connection.refresh_tokens.upsert({ - where: { - user_id: userId, - }, - update: { - token, - expires_at: expiresAt, - }, - create: { - user_id: userId, - token, - expires_at: expiresAt, - }, - select: { user_id: true, token: true }, - }); - } -} - -export const CreateRefreshTokenRepository = new Repository('refresh_tokens'); \ No newline at end of file diff --git a/backend/src/core/modules/auth/repository/forgot-password.repository.js b/backend/src/core/modules/auth/repository/forgot-password.repository.js deleted file mode 100644 index 421de97..0000000 --- a/backend/src/core/modules/auth/repository/forgot-password.repository.js +++ /dev/null @@ -1,19 +0,0 @@ -import connection from 'core/database'; -import { DataRepository } from 'packages/restBuilder/core/dataHandler'; - -class Repository extends DataRepository { - async forgotPassword(userId, token, expiresAt, trx = null) { - return await connection.users.update({ - where: { - id: userId, - }, - data: { - password_reset_token: token, // chỗ này em bỏ vào password_reset_token vì không có cái column riêng cho otp thường - password_reset_expiry: expiresAt, // tương tự - // cái này chắc ảnh hưởng bảo mật nhiều, nhưng mà em không rõ có cách nào khác. (@c quynh) - }, - }); - }; -} - -export const ForgotPasswordRepository = new Repository('users'); \ No newline at end of file diff --git a/backend/src/core/modules/auth/repository/index.js b/backend/src/core/modules/auth/repository/index.js deleted file mode 100644 index e16f863..0000000 --- a/backend/src/core/modules/auth/repository/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export * from './create-refresh-token.repository'; -export * from './forgot-password.repository'; -export * from './password-reset.repository'; \ No newline at end of file diff --git a/backend/src/core/modules/auth/repository/password-reset.repository.js b/backend/src/core/modules/auth/repository/password-reset.repository.js deleted file mode 100644 index ecea31a..0000000 --- a/backend/src/core/modules/auth/repository/password-reset.repository.js +++ /dev/null @@ -1,19 +0,0 @@ -// import { UserRepository } from 'core/modules/user/user.repository'; -import connection from 'core/database'; -import { DataRepository } from 'packages/restBuilder/core/dataHandler'; - -class Repository extends DataRepository { - async passwordResetCreateToken(userId, token, expiresAt, trx = null) { - return await connection.users.update({ - where: { - id: userId, - }, - data: { - password_reset_token: token, - password_reset_expiry: expiresAt, - }, - }); - }; -} - -export const PasswordResetRepository = new Repository('users'); \ No newline at end of file diff --git a/backend/src/core/modules/auth/service/auth.service.js b/backend/src/core/modules/auth/service/auth.service.js index 1b33283..de121bc 100644 --- a/backend/src/core/modules/auth/service/auth.service.js +++ b/backend/src/core/modules/auth/service/auth.service.js @@ -8,8 +8,8 @@ import { BcryptService } from './bcrypt.service'; import { JwtService } from './jwt.service'; import { UserRepository } from '../../user/user.repository'; import { UnAuthorizedException, DuplicateException, BadRequestException } from '../../../../packages/httpException'; -import { CreateRefreshTokenRepository, PasswordResetRepository, ForgotPasswordRepository } from '../repository'; -import { re } from 'prettier'; +import { authMiddleware, rbac } from '../middleware' +import { MediaService } from 'core/modules/document'; const REFRESH_TOKEN_EXPIRY = 24 * 60 * 60 * 1000; // 1 day const FORGOT_PASSWORD_TOKEN_EXPIRY = 15 * 60 * 1000; // 15 minute @@ -20,9 +20,6 @@ class Service { this.userRepository = UserRepository; this.jwtService = JwtService; this.bcryptService = BcryptService; - this.createRefreshTokenRepository = CreateRefreshTokenRepository; - this.passwordResetRepository = PasswordResetRepository; - this.forgotPasswordRepository = ForgotPasswordRepository; } async register(registerDto) { @@ -32,7 +29,8 @@ class Service { } let existingRole = await connection.roles.findFirst({ - where: {name: registerDto.role} + where: {name: registerDto.role}, + select: {id: true}, }); if (!existingRole) { throw new BadRequestException("Role must be one of: USER, LAWYER, ADMIN"); @@ -50,9 +48,27 @@ class Service { referral_code: registerDto.referralCode, full_name: registerDto.fullName, }, - select: { id: true } + select: { id: true }, }); + if (registerDto.role == 'LAWYER') { + await connection.lawyer_details.create({ + data: { + user_id: result.id, + license_number: registerDto.licenseNumber, + }, + }); + await connection.lawyer_certificates.create({ + data: { + lawyer_id: result.id, + issued_by: registerDto.licenseIssuer, + issue_date: new Date(registerDto.licenseIssueDate).toISOString(), + certificate_name: "placeholder", // thieu cai nay trong luc nhap. + file_url: registerDto.licenseFile, // cai nay chua duoc su dung, can phai chuyen qua url. + }, + }); + } + const { token: refreshToken } = await this.#createRefreshToken(result.id); const accessToken = this.jwtService.sign(JwtPayload({ id: result.id, role: [registerDto.role] })); @@ -81,12 +97,17 @@ class Service { throw new UnAuthorizedException('Email or password is incorrect'); } + if (loginDto.role !== user.roles.name) { + throw new UnAuthorizedException("Unauthorized or insufficient permissions. Please login according to your role.") + } + if (loginDto.role === "LAWYER") { let existingLicenseNumber = await connection.lawyer_details.findFirst({ - where: {user_id: user.id} + where: {user_id: user.id}, + select: {license_number: true}, }); - if (existingLicenseNumber.license_number !== loginDto.license_number) { + if (existingLicenseNumber.license_number !== loginDto.licenseNumber) { throw new UnAuthorizedException('Invalid credentials') } } @@ -121,7 +142,14 @@ class Service { const otp = crypto.randomInt(100000, 999999).toString(); const expiresAt = new Date(Date.now() + FORGOT_PASSWORD_TOKEN_EXPIRY); - await this.forgotPasswordRepository.forgotPassword(existingEmail.id, otp, expiresAt); + await connection.users.update({ + where: { id: existingEmail.id }, + data: { + password_reset_token: otp, // chỗ này em bỏ vào password_reset_token vì không có cái column riêng cho otp thường + password_reset_expiry: expiresAt, // tương tự + // cái này chắc ảnh hưởng bảo mật nhiều, nhưng mà em không rõ có cách nào khác. (@c quynh) + }, + }); return { message: "OTP has been sent to your email.", @@ -140,7 +168,7 @@ class Service { }); if (existingOtp.password_reset_expiry < Date.now()) { - throw new UnAuthorizedException("OTP has expired " + existingOtp.password_reset_expiry); + throw new UnAuthorizedException("OTP has expired"); } if (existingOtp.password_reset_token != verifyOtpDto.otp) { @@ -261,7 +289,12 @@ class Service { async #createRefreshToken(userId) { const token = crypto.randomBytes(32).toString('hex'); const expiresAt = new Date(Date.now() + REFRESH_TOKEN_EXPIRY); - const record = await this.createRefreshTokenRepository.createToken(userId, token, expiresAt); + const record = await connection.refresh_tokens.upsert({ + where: { user_id: userId }, + update: { token, expires_at: expiresAt }, + create: { user_id: userId, token, expires_at: expiresAt }, + select: { user_id: true, token: true }, + }); return { id: record.user_id || record, token: record.token || token @@ -271,12 +304,23 @@ class Service { async #createPasswordResetToken(userId) { const token = crypto.randomBytes(32).toString('hex'); const expiresAt = new Date(Date.now() + PASSWORD_RESET_TOKEN_EXPIRY); - const record = await this.passwordResetRepository.passwordResetCreateToken(userId, token, expiresAt); + const record = await connection.users.update({ + where: { id: userId }, + data: { + password_reset_token: token, + password_reset_expiry: expiresAt, + }, + }); return { id: record.user_id || record, token: record.token || token }; } + + async uploadAvatar(files, folderName = '') { + const uploadTasks = files.map(file => MediaService.uploadOne(file, folderName)); + return Promise.all(uploadTasks); + } } export const AuthService = new Service(); From ee22f34943ddf671bb4d483ee135bc936831d433 Mon Sep 17 00:00:00 2001 From: DQGiac Date: Fri, 15 May 2026 15:26:38 +0700 Subject: [PATCH 11/26] feat[FI-12]: added access and refresh tokens --- .../repository/refresh-token.repository.js | 21 +++++++++++++++++++ .../core/modules/auth/service/auth.service.js | 12 +++++++++++ 2 files changed, 33 insertions(+) create mode 100644 backend/src/core/modules/auth/repository/refresh-token.repository.js diff --git a/backend/src/core/modules/auth/repository/refresh-token.repository.js b/backend/src/core/modules/auth/repository/refresh-token.repository.js new file mode 100644 index 0000000..ad729c0 --- /dev/null +++ b/backend/src/core/modules/auth/repository/refresh-token.repository.js @@ -0,0 +1,21 @@ +import { DataRepository } from 'packages/restBuilder/core/dataHandler/data.repository'; +import connection from 'core/database'; + +class Repository extends DataRepository { + async createToken(userId, token, expiresAt, trx = null) { + const result = await connection.$transaction(async (trx) => { + const insertedUser = await trx.refresh_tokens.create({ + data: { + user_id: userId, + token, + expires_at: expiresAt, + }, + select: { user_id: true, token: true } + }); + return insertedUser; + }); + return result; + } +} + +export const RefreshTokenRepository = new Repository('refresh_tokens'); \ No newline at end of file diff --git a/backend/src/core/modules/auth/service/auth.service.js b/backend/src/core/modules/auth/service/auth.service.js index ced751d..32da7e7 100644 --- a/backend/src/core/modules/auth/service/auth.service.js +++ b/backend/src/core/modules/auth/service/auth.service.js @@ -20,6 +20,7 @@ class Service { this.userRepository = UserRepository; this.jwtService = JwtService; this.bcryptService = BcryptService; + this.refreshTokenRepository = RefreshTokenRepository; } async register(registerDto) { @@ -286,6 +287,17 @@ class Service { token: record.token || token }; } + + async #createRefreshToken(userId) { + // Tao chuoi ngau nhien 32 bytes (64 ky tu hex) cho Refresh Token + const token = crypto.randomBytes(32).toString('hex'); + const expiresAt = new Date(Date.now() + REFRESH_TOKEN_EXPIRY); + const record = await this.refreshTokenRepository.createToken(userId, token, expiresAt); + return { + id: record.user_id || record, + token: record.token || token + }; + } } export const AuthService = new Service(); From 6b70b8f6e28be26e2b33c9b862f2aceeb5a38620 Mon Sep 17 00:00:00 2001 From: DQGiac Date: Sat, 16 May 2026 14:01:57 +0700 Subject: [PATCH 12/26] feat[FI-12]: Separated login for user and lawyer. Cleaned up code. --- backend/src/core/api/auth/auth.controller.js | 8 ++++++-- backend/src/core/api/auth/auth.resolver.js | 13 ++++++++++--- .../auth/dto/{login.dto.js => login-user.dto.js} | 2 +- backend/src/core/modules/auth/interceptor/index.js | 7 +++++++ .../auth/interceptor/login-lawyer.interceptor.js | 11 +++++++++++ ...gin.interceptor.js => login-user.interceptor.js} | 2 +- .../src/core/modules/auth/service/auth.service.js | 11 ++++++++++- 7 files changed, 46 insertions(+), 8 deletions(-) rename backend/src/core/modules/auth/dto/{login.dto.js => login-user.dto.js} (89%) create mode 100644 backend/src/core/modules/auth/interceptor/login-lawyer.interceptor.js rename backend/src/core/modules/auth/interceptor/{login.interceptor.js => login-user.interceptor.js} (85%) diff --git a/backend/src/core/api/auth/auth.controller.js b/backend/src/core/api/auth/auth.controller.js index b4029f1..6365276 100644 --- a/backend/src/core/api/auth/auth.controller.js +++ b/backend/src/core/api/auth/auth.controller.js @@ -13,8 +13,12 @@ class Controller { return ValidHttpResponse.toOkResponse(data); } - login = async req => { - const data = await this.service.login(LoginDto(req.body)); + loginUser = async req => { + const data = await this.service.login(LoginUserDto(req.body)); + return ValidHttpResponse.toOkResponse(data); + } + loginLawyer = async req => { + const data = await this.service.login(LoginLawyerDto(req.body)); return ValidHttpResponse.toOkResponse(data); } diff --git a/backend/src/core/api/auth/auth.resolver.js b/backend/src/core/api/auth/auth.resolver.js index 324b65e..b78bf85 100644 --- a/backend/src/core/api/auth/auth.resolver.js +++ b/backend/src/core/api/auth/auth.resolver.js @@ -25,9 +25,16 @@ export const AuthResolver = Module.builder() { route: '/login', method: 'post', - interceptors: [LoginInterceptor], - body: 'LoginDto', - controller: AuthController.login, + interceptors: [LoginUserInterceptor], + body: 'LoginUserDto', + controller: AuthController.loginUser, + }, + { + route: '/login', + method: 'post', + interceptors: [LoginLawyerInterceptor], + body: 'LoginLawyerDto', + controller: AuthController.loginLawyer, }, { route: '/register', diff --git a/backend/src/core/modules/auth/dto/login.dto.js b/backend/src/core/modules/auth/dto/login-user.dto.js similarity index 89% rename from backend/src/core/modules/auth/dto/login.dto.js rename to backend/src/core/modules/auth/dto/login-user.dto.js index 9a2d28a..8dd724d 100644 --- a/backend/src/core/modules/auth/dto/login.dto.js +++ b/backend/src/core/modules/auth/dto/login-user.dto.js @@ -6,7 +6,7 @@ ApiDocument.addModel('LoginDto', { password: SwaggerDocument.ApiProperty({ type: 'string' }), }); -export const LoginDto = body => ({ +export const LoginUserDto = body => ({ email: body.email, password: body.password, }); \ No newline at end of file diff --git a/backend/src/core/modules/auth/interceptor/index.js b/backend/src/core/modules/auth/interceptor/index.js index 7320998..8d0c12c 100644 --- a/backend/src/core/modules/auth/interceptor/index.js +++ b/backend/src/core/modules/auth/interceptor/index.js @@ -1,3 +1,4 @@ +<<<<<<< HEAD export * from './login.interceptor'; export * from './register.interceptor'; export * from './get-my-profile.interceptor'; @@ -6,3 +7,9 @@ export * from './forgot-password.interceptor'; export * from './verify-otp.interceptor'; export * from './reset-password.interceptor'; export * from './refresh-token.interceptor'; +======= +export * from './login-user.interceptor'; +export * from './login-lawyer.interceptor'; +export * from './register-lawyer.interceptor'; +export * from './register-user.interceptor'; +>>>>>>> b7bdb39 (feat[FI-12]: Separated login for user and lawyer. Cleaned up code.) diff --git a/backend/src/core/modules/auth/interceptor/login-lawyer.interceptor.js b/backend/src/core/modules/auth/interceptor/login-lawyer.interceptor.js new file mode 100644 index 0000000..63ade69 --- /dev/null +++ b/backend/src/core/modules/auth/interceptor/login-lawyer.interceptor.js @@ -0,0 +1,11 @@ +import { DefaultValidatorInterceptor } from 'core/infrastructure/interceptor'; +import { JoiUtils } from 'core/utils'; +import Joi from 'joi'; + +export const LoginLawyerInterceptor = new DefaultValidatorInterceptor( + Joi.object({ + email: JoiUtils.email().required(), + password: JoiUtils.requiredString(), + licenseNumber: JoiUtils.requiredString(), + }) +); diff --git a/backend/src/core/modules/auth/interceptor/login.interceptor.js b/backend/src/core/modules/auth/interceptor/login-user.interceptor.js similarity index 85% rename from backend/src/core/modules/auth/interceptor/login.interceptor.js rename to backend/src/core/modules/auth/interceptor/login-user.interceptor.js index 6421fd7..6a8917a 100644 --- a/backend/src/core/modules/auth/interceptor/login.interceptor.js +++ b/backend/src/core/modules/auth/interceptor/login-user.interceptor.js @@ -2,7 +2,7 @@ import { DefaultValidatorInterceptor } from 'core/infrastructure/interceptor'; import { JoiUtils } from 'core/utils'; import Joi from 'joi'; -export const LoginInterceptor = new DefaultValidatorInterceptor( +export const LoginUserInterceptor = new DefaultValidatorInterceptor( Joi.object({ email: JoiUtils.email().required().pattern(/^.*@.*\.(com|net|org)$/).message({ 'string.pattern.base': 'Email must be a valid email address.' diff --git a/backend/src/core/modules/auth/service/auth.service.js b/backend/src/core/modules/auth/service/auth.service.js index 32da7e7..5316683 100644 --- a/backend/src/core/modules/auth/service/auth.service.js +++ b/backend/src/core/modules/auth/service/auth.service.js @@ -75,6 +75,16 @@ class Service { throw new UnAuthorizedException('Email or password is incorrect'); } + if (loginDto.role === "LAWYER") { + let existingLicenseNumber = await connection.lawyer_details.findFirst({ + where: {user_id: user.id} + }); + + if (existingLicenseNumber.license_number !== loginDto.license_number) { + throw new UnAuthorizedException('Invalid credentials') + } + } + const userData = { user_id: user.id, email: user.email, @@ -289,7 +299,6 @@ class Service { } async #createRefreshToken(userId) { - // Tao chuoi ngau nhien 32 bytes (64 ky tu hex) cho Refresh Token const token = crypto.randomBytes(32).toString('hex'); const expiresAt = new Date(Date.now() + REFRESH_TOKEN_EXPIRY); const record = await this.refreshTokenRepository.createToken(userId, token, expiresAt); From eed935a37a811fc8145b7988b89c179645882dcc Mon Sep 17 00:00:00 2001 From: DQGiac Date: Mon, 18 May 2026 00:09:35 +0700 Subject: [PATCH 13/26] feat[FI-12]: Added forgot password and verify OTP. Cleaned up code from login and register. --- backend/src/core/api/auth/auth.resolver.js | 4 ++-- backend/src/core/modules/auth/index.js | 1 + .../core/modules/auth/interceptor/index.js | 9 +-------- .../interceptor/verify-otp.interceptor.js | 4 ++++ .../repository/forgot-password.repository.js | 19 +++++++++++++++++++ .../src/core/modules/auth/repository/index.js | 3 +++ .../repository/password-reset.repository.js | 19 +++++++++++++++++++ .../repository/refresh-token.repository.js | 18 +++++++----------- .../core/modules/auth/service/auth.service.js | 12 ++++++++++++ 9 files changed, 68 insertions(+), 21 deletions(-) create mode 100644 backend/src/core/modules/auth/repository/forgot-password.repository.js create mode 100644 backend/src/core/modules/auth/repository/index.js create mode 100644 backend/src/core/modules/auth/repository/password-reset.repository.js diff --git a/backend/src/core/api/auth/auth.resolver.js b/backend/src/core/api/auth/auth.resolver.js index b78bf85..4aa988a 100644 --- a/backend/src/core/api/auth/auth.resolver.js +++ b/backend/src/core/api/auth/auth.resolver.js @@ -23,14 +23,14 @@ export const AuthResolver = Module.builder() }) .register([ { - route: '/login', + route: '/login_user', method: 'post', interceptors: [LoginUserInterceptor], body: 'LoginUserDto', controller: AuthController.loginUser, }, { - route: '/login', + route: '/login_lawyer', method: 'post', interceptors: [LoginLawyerInterceptor], body: 'LoginLawyerDto', diff --git a/backend/src/core/modules/auth/index.js b/backend/src/core/modules/auth/index.js index 646f1aa..028de5f 100644 --- a/backend/src/core/modules/auth/index.js +++ b/backend/src/core/modules/auth/index.js @@ -1,3 +1,4 @@ export * from './dto'; export * from './service'; export * from './interceptor'; +export * from './repository'; \ No newline at end of file diff --git a/backend/src/core/modules/auth/interceptor/index.js b/backend/src/core/modules/auth/interceptor/index.js index 8d0c12c..eb77fd5 100644 --- a/backend/src/core/modules/auth/interceptor/index.js +++ b/backend/src/core/modules/auth/interceptor/index.js @@ -1,4 +1,3 @@ -<<<<<<< HEAD export * from './login.interceptor'; export * from './register.interceptor'; export * from './get-my-profile.interceptor'; @@ -6,10 +5,4 @@ export * from './update-my-profile.interceptor'; export * from './forgot-password.interceptor'; export * from './verify-otp.interceptor'; export * from './reset-password.interceptor'; -export * from './refresh-token.interceptor'; -======= -export * from './login-user.interceptor'; -export * from './login-lawyer.interceptor'; -export * from './register-lawyer.interceptor'; -export * from './register-user.interceptor'; ->>>>>>> b7bdb39 (feat[FI-12]: Separated login for user and lawyer. Cleaned up code.) +export * from './refresh-token.interceptor'; \ No newline at end of file diff --git a/backend/src/core/modules/auth/interceptor/verify-otp.interceptor.js b/backend/src/core/modules/auth/interceptor/verify-otp.interceptor.js index de5506f..c2f11b2 100644 --- a/backend/src/core/modules/auth/interceptor/verify-otp.interceptor.js +++ b/backend/src/core/modules/auth/interceptor/verify-otp.interceptor.js @@ -4,9 +4,13 @@ import Joi from 'joi'; export const VerifyOtpInterceptor = new DefaultValidatorInterceptor( Joi.object({ +<<<<<<< HEAD email: JoiUtils.email().required().pattern(/^.*@.*\.(com|net|org)$/).message({ 'string.pattern.base': 'Email must be a valid email address.' }), +======= + email: JoiUtils.email().required(), +>>>>>>> f948f5c (feat[FI-12]: Added forgot password and verify OTP. Cleaned up code from login and register.) otp: JoiUtils.requiredString(), }) ); \ No newline at end of file diff --git a/backend/src/core/modules/auth/repository/forgot-password.repository.js b/backend/src/core/modules/auth/repository/forgot-password.repository.js new file mode 100644 index 0000000..421de97 --- /dev/null +++ b/backend/src/core/modules/auth/repository/forgot-password.repository.js @@ -0,0 +1,19 @@ +import connection from 'core/database'; +import { DataRepository } from 'packages/restBuilder/core/dataHandler'; + +class Repository extends DataRepository { + async forgotPassword(userId, token, expiresAt, trx = null) { + return await connection.users.update({ + where: { + id: userId, + }, + data: { + password_reset_token: token, // chỗ này em bỏ vào password_reset_token vì không có cái column riêng cho otp thường + password_reset_expiry: expiresAt, // tương tự + // cái này chắc ảnh hưởng bảo mật nhiều, nhưng mà em không rõ có cách nào khác. (@c quynh) + }, + }); + }; +} + +export const ForgotPasswordRepository = new Repository('users'); \ No newline at end of file diff --git a/backend/src/core/modules/auth/repository/index.js b/backend/src/core/modules/auth/repository/index.js new file mode 100644 index 0000000..efefad1 --- /dev/null +++ b/backend/src/core/modules/auth/repository/index.js @@ -0,0 +1,3 @@ +export * from './refresh-token.repository'; +export * from './forgot-password.repository'; +export * from './password-reset.repository'; \ No newline at end of file diff --git a/backend/src/core/modules/auth/repository/password-reset.repository.js b/backend/src/core/modules/auth/repository/password-reset.repository.js new file mode 100644 index 0000000..b4a58a5 --- /dev/null +++ b/backend/src/core/modules/auth/repository/password-reset.repository.js @@ -0,0 +1,19 @@ +// import { UserRepository } from 'core/modules/user/user.repository'; +import connection from 'core/database'; +import { DataRepository } from 'packages/restBuilder/core/dataHandler'; + +class Repository extends DataRepository { + async passwordReset(userId, token, expiresAt, trx = null) { + return await connection.users.update({ + where: { + id: userId, + }, + data: { + password_reset_token: token, + password_reset_expiry: expiresAt, + }, + }); + }; +} + +export const PasswordResetRepository = new Repository('users'); \ No newline at end of file diff --git a/backend/src/core/modules/auth/repository/refresh-token.repository.js b/backend/src/core/modules/auth/repository/refresh-token.repository.js index ad729c0..b389fc6 100644 --- a/backend/src/core/modules/auth/repository/refresh-token.repository.js +++ b/backend/src/core/modules/auth/repository/refresh-token.repository.js @@ -3,18 +3,14 @@ import connection from 'core/database'; class Repository extends DataRepository { async createToken(userId, token, expiresAt, trx = null) { - const result = await connection.$transaction(async (trx) => { - const insertedUser = await trx.refresh_tokens.create({ - data: { - user_id: userId, - token, - expires_at: expiresAt, - }, - select: { user_id: true, token: true } - }); - return insertedUser; + return await connection.refresh_tokens.create({ + data: { + user_id: userId, + token, + expires_at: expiresAt, + }, + select: { user_id: true, token: true }, }); - return result; } } diff --git a/backend/src/core/modules/auth/service/auth.service.js b/backend/src/core/modules/auth/service/auth.service.js index 5316683..0792d41 100644 --- a/backend/src/core/modules/auth/service/auth.service.js +++ b/backend/src/core/modules/auth/service/auth.service.js @@ -21,6 +21,8 @@ class Service { this.jwtService = JwtService; this.bcryptService = BcryptService; this.refreshTokenRepository = RefreshTokenRepository; + this.passwordResetRepository = PasswordResetRepository; + this.forgotPasswordRepository = ForgotPasswordRepository; } async register(registerDto) { @@ -307,6 +309,16 @@ class Service { token: record.token || token }; } + + async #createPasswordResetToken(userId) { + const token = crypto.randomBytes(32).toString('hex'); + const expiresAt = new Date(Date.now() + PASSWORD_RESET_TOKEN_EXPIRY); + const record = await this.passwordResetRepository.passwordReset(userId, token, expiresAt); + return { + id: record.user_id || record, + token: record.token || token + }; + } } export const AuthService = new Service(); From 298549b5d4dff70750e59ebcbe00103e7fc4d0e3 Mon Sep 17 00:00:00 2001 From: DQGiac Date: Mon, 18 May 2026 09:19:53 +0700 Subject: [PATCH 14/26] feat[FI-12]: Added reset password. Changed interceptors to not accept invalid password. Cleaned up code. --- backend/src/core/api/auth/auth.controller.js | 9 ++------- backend/src/core/api/auth/auth.resolver.js | 15 ++++----------- .../src/core/modules/auth/interceptor/index.js | 2 +- .../auth/interceptor/login-lawyer.interceptor.js | 11 ----------- ...n-user.interceptor.js => login.interceptor.js} | 0 .../auth/interceptor/verify-otp.interceptor.js | 4 ---- .../auth/repository/password-reset.repository.js | 2 +- .../src/core/modules/auth/service/auth.service.js | 2 +- 8 files changed, 9 insertions(+), 36 deletions(-) delete mode 100644 backend/src/core/modules/auth/interceptor/login-lawyer.interceptor.js rename backend/src/core/modules/auth/interceptor/{login-user.interceptor.js => login.interceptor.js} (100%) diff --git a/backend/src/core/api/auth/auth.controller.js b/backend/src/core/api/auth/auth.controller.js index 6365276..edc7a22 100644 --- a/backend/src/core/api/auth/auth.controller.js +++ b/backend/src/core/api/auth/auth.controller.js @@ -13,15 +13,10 @@ class Controller { return ValidHttpResponse.toOkResponse(data); } - loginUser = async req => { - const data = await this.service.login(LoginUserDto(req.body)); + login = async req => { + const data = await this.service.login(LoginDto(req.body)); return ValidHttpResponse.toOkResponse(data); } - loginLawyer = async req => { - const data = await this.service.login(LoginLawyerDto(req.body)); - return ValidHttpResponse.toOkResponse(data); - } - getMyProfile = async req => { const data = await this.service.getMyProfile(req.user.payload.id); diff --git a/backend/src/core/api/auth/auth.resolver.js b/backend/src/core/api/auth/auth.resolver.js index 4aa988a..324b65e 100644 --- a/backend/src/core/api/auth/auth.resolver.js +++ b/backend/src/core/api/auth/auth.resolver.js @@ -23,18 +23,11 @@ export const AuthResolver = Module.builder() }) .register([ { - route: '/login_user', + route: '/login', method: 'post', - interceptors: [LoginUserInterceptor], - body: 'LoginUserDto', - controller: AuthController.loginUser, - }, - { - route: '/login_lawyer', - method: 'post', - interceptors: [LoginLawyerInterceptor], - body: 'LoginLawyerDto', - controller: AuthController.loginLawyer, + interceptors: [LoginInterceptor], + body: 'LoginDto', + controller: AuthController.login, }, { route: '/register', diff --git a/backend/src/core/modules/auth/interceptor/index.js b/backend/src/core/modules/auth/interceptor/index.js index eb77fd5..7320998 100644 --- a/backend/src/core/modules/auth/interceptor/index.js +++ b/backend/src/core/modules/auth/interceptor/index.js @@ -5,4 +5,4 @@ export * from './update-my-profile.interceptor'; export * from './forgot-password.interceptor'; export * from './verify-otp.interceptor'; export * from './reset-password.interceptor'; -export * from './refresh-token.interceptor'; \ No newline at end of file +export * from './refresh-token.interceptor'; diff --git a/backend/src/core/modules/auth/interceptor/login-lawyer.interceptor.js b/backend/src/core/modules/auth/interceptor/login-lawyer.interceptor.js deleted file mode 100644 index 63ade69..0000000 --- a/backend/src/core/modules/auth/interceptor/login-lawyer.interceptor.js +++ /dev/null @@ -1,11 +0,0 @@ -import { DefaultValidatorInterceptor } from 'core/infrastructure/interceptor'; -import { JoiUtils } from 'core/utils'; -import Joi from 'joi'; - -export const LoginLawyerInterceptor = new DefaultValidatorInterceptor( - Joi.object({ - email: JoiUtils.email().required(), - password: JoiUtils.requiredString(), - licenseNumber: JoiUtils.requiredString(), - }) -); diff --git a/backend/src/core/modules/auth/interceptor/login-user.interceptor.js b/backend/src/core/modules/auth/interceptor/login.interceptor.js similarity index 100% rename from backend/src/core/modules/auth/interceptor/login-user.interceptor.js rename to backend/src/core/modules/auth/interceptor/login.interceptor.js diff --git a/backend/src/core/modules/auth/interceptor/verify-otp.interceptor.js b/backend/src/core/modules/auth/interceptor/verify-otp.interceptor.js index c2f11b2..de5506f 100644 --- a/backend/src/core/modules/auth/interceptor/verify-otp.interceptor.js +++ b/backend/src/core/modules/auth/interceptor/verify-otp.interceptor.js @@ -4,13 +4,9 @@ import Joi from 'joi'; export const VerifyOtpInterceptor = new DefaultValidatorInterceptor( Joi.object({ -<<<<<<< HEAD email: JoiUtils.email().required().pattern(/^.*@.*\.(com|net|org)$/).message({ 'string.pattern.base': 'Email must be a valid email address.' }), -======= - email: JoiUtils.email().required(), ->>>>>>> f948f5c (feat[FI-12]: Added forgot password and verify OTP. Cleaned up code from login and register.) otp: JoiUtils.requiredString(), }) ); \ No newline at end of file diff --git a/backend/src/core/modules/auth/repository/password-reset.repository.js b/backend/src/core/modules/auth/repository/password-reset.repository.js index b4a58a5..ecea31a 100644 --- a/backend/src/core/modules/auth/repository/password-reset.repository.js +++ b/backend/src/core/modules/auth/repository/password-reset.repository.js @@ -3,7 +3,7 @@ import connection from 'core/database'; import { DataRepository } from 'packages/restBuilder/core/dataHandler'; class Repository extends DataRepository { - async passwordReset(userId, token, expiresAt, trx = null) { + async passwordResetCreateToken(userId, token, expiresAt, trx = null) { return await connection.users.update({ where: { id: userId, diff --git a/backend/src/core/modules/auth/service/auth.service.js b/backend/src/core/modules/auth/service/auth.service.js index 0792d41..277643c 100644 --- a/backend/src/core/modules/auth/service/auth.service.js +++ b/backend/src/core/modules/auth/service/auth.service.js @@ -313,7 +313,7 @@ class Service { async #createPasswordResetToken(userId) { const token = crypto.randomBytes(32).toString('hex'); const expiresAt = new Date(Date.now() + PASSWORD_RESET_TOKEN_EXPIRY); - const record = await this.passwordResetRepository.passwordReset(userId, token, expiresAt); + const record = await this.passwordResetRepository.passwordResetCreateToken(userId, token, expiresAt); return { id: record.user_id || record, token: record.token || token From e530689dd1d3a2054368ddd5fc52f3030d8f6070 Mon Sep 17 00:00:00 2001 From: DQGiac Date: Mon, 18 May 2026 15:42:32 +0700 Subject: [PATCH 15/26] feat[FI-12}: Added viewing and updating personal data. Changed formats of date and email for input --- backend/src/core/api/auth/auth.resolver.js | 16 ++++++++++++++++ backend/src/core/modules/auth/index.js | 3 +-- .../repository/forgot-password.repository.js | 19 ------------------- .../src/core/modules/auth/repository/index.js | 3 --- .../repository/password-reset.repository.js | 19 ------------------- .../repository/refresh-token.repository.js | 17 ----------------- 6 files changed, 17 insertions(+), 60 deletions(-) delete mode 100644 backend/src/core/modules/auth/repository/forgot-password.repository.js delete mode 100644 backend/src/core/modules/auth/repository/index.js delete mode 100644 backend/src/core/modules/auth/repository/password-reset.repository.js delete mode 100644 backend/src/core/modules/auth/repository/refresh-token.repository.js diff --git a/backend/src/core/api/auth/auth.resolver.js b/backend/src/core/api/auth/auth.resolver.js index 324b65e..1b72783 100644 --- a/backend/src/core/api/auth/auth.resolver.js +++ b/backend/src/core/api/auth/auth.resolver.js @@ -54,6 +54,22 @@ export const AuthResolver = Module.builder() controller: AuthController.updateMyProfile, preAuthorization: true, }, + { + route: '/me/:id', + method: 'get', + params: [GetMyProfileParams], + interceptors: [GetMyProfileInterceptor], + controller: AuthController.getMyProfile, + preAuthorization: true, + }, + { + route: '/update_my_profile', + method: 'post', + interceptors: [UpdateMyProfileInterceptor], + body: 'UpdateMyProfileDto', + controller: AuthController.updateMyProfile, + preAuthorization: true, + }, { route: '/forgot_password', method: 'post', diff --git a/backend/src/core/modules/auth/index.js b/backend/src/core/modules/auth/index.js index 028de5f..c7aec32 100644 --- a/backend/src/core/modules/auth/index.js +++ b/backend/src/core/modules/auth/index.js @@ -1,4 +1,3 @@ export * from './dto'; export * from './service'; -export * from './interceptor'; -export * from './repository'; \ No newline at end of file +export * from './interceptor'; \ No newline at end of file diff --git a/backend/src/core/modules/auth/repository/forgot-password.repository.js b/backend/src/core/modules/auth/repository/forgot-password.repository.js deleted file mode 100644 index 421de97..0000000 --- a/backend/src/core/modules/auth/repository/forgot-password.repository.js +++ /dev/null @@ -1,19 +0,0 @@ -import connection from 'core/database'; -import { DataRepository } from 'packages/restBuilder/core/dataHandler'; - -class Repository extends DataRepository { - async forgotPassword(userId, token, expiresAt, trx = null) { - return await connection.users.update({ - where: { - id: userId, - }, - data: { - password_reset_token: token, // chỗ này em bỏ vào password_reset_token vì không có cái column riêng cho otp thường - password_reset_expiry: expiresAt, // tương tự - // cái này chắc ảnh hưởng bảo mật nhiều, nhưng mà em không rõ có cách nào khác. (@c quynh) - }, - }); - }; -} - -export const ForgotPasswordRepository = new Repository('users'); \ No newline at end of file diff --git a/backend/src/core/modules/auth/repository/index.js b/backend/src/core/modules/auth/repository/index.js deleted file mode 100644 index efefad1..0000000 --- a/backend/src/core/modules/auth/repository/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export * from './refresh-token.repository'; -export * from './forgot-password.repository'; -export * from './password-reset.repository'; \ No newline at end of file diff --git a/backend/src/core/modules/auth/repository/password-reset.repository.js b/backend/src/core/modules/auth/repository/password-reset.repository.js deleted file mode 100644 index ecea31a..0000000 --- a/backend/src/core/modules/auth/repository/password-reset.repository.js +++ /dev/null @@ -1,19 +0,0 @@ -// import { UserRepository } from 'core/modules/user/user.repository'; -import connection from 'core/database'; -import { DataRepository } from 'packages/restBuilder/core/dataHandler'; - -class Repository extends DataRepository { - async passwordResetCreateToken(userId, token, expiresAt, trx = null) { - return await connection.users.update({ - where: { - id: userId, - }, - data: { - password_reset_token: token, - password_reset_expiry: expiresAt, - }, - }); - }; -} - -export const PasswordResetRepository = new Repository('users'); \ No newline at end of file diff --git a/backend/src/core/modules/auth/repository/refresh-token.repository.js b/backend/src/core/modules/auth/repository/refresh-token.repository.js deleted file mode 100644 index b389fc6..0000000 --- a/backend/src/core/modules/auth/repository/refresh-token.repository.js +++ /dev/null @@ -1,17 +0,0 @@ -import { DataRepository } from 'packages/restBuilder/core/dataHandler/data.repository'; -import connection from 'core/database'; - -class Repository extends DataRepository { - async createToken(userId, token, expiresAt, trx = null) { - return await connection.refresh_tokens.create({ - data: { - user_id: userId, - token, - expires_at: expiresAt, - }, - select: { user_id: true, token: true }, - }); - } -} - -export const RefreshTokenRepository = new Repository('refresh_tokens'); \ No newline at end of file From 9b6e839cd1a2dc4e84c0f3221ecd9e24d38beb16 Mon Sep 17 00:00:00 2001 From: DQGiac Date: Mon, 18 May 2026 17:38:04 +0700 Subject: [PATCH 16/26] feat[FI-12]: Added refresh-token. Renamed confusing functions. --- .../create-refresh-token.repository.js | 24 +++++++++++++++++++ .../src/core/modules/auth/repository/index.js | 3 +++ .../core/modules/auth/service/auth.service.js | 4 ++-- 3 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 backend/src/core/modules/auth/repository/create-refresh-token.repository.js create mode 100644 backend/src/core/modules/auth/repository/index.js diff --git a/backend/src/core/modules/auth/repository/create-refresh-token.repository.js b/backend/src/core/modules/auth/repository/create-refresh-token.repository.js new file mode 100644 index 0000000..c5d30d9 --- /dev/null +++ b/backend/src/core/modules/auth/repository/create-refresh-token.repository.js @@ -0,0 +1,24 @@ +import { DataRepository } from 'packages/restBuilder/core/dataHandler/data.repository'; +import connection from 'core/database'; + +class Repository extends DataRepository { + async createToken(userId, token, expiresAt, trx = null) { + return await connection.refresh_tokens.upsert({ + where: { + user_id: userId, + }, + update: { + token, + expires_at: expiresAt, + }, + create: { + user_id: userId, + token, + expires_at: expiresAt, + }, + select: { user_id: true, token: true }, + }); + } +} + +export const CreateRefreshTokenRepository = new Repository('refresh_tokens'); \ No newline at end of file diff --git a/backend/src/core/modules/auth/repository/index.js b/backend/src/core/modules/auth/repository/index.js new file mode 100644 index 0000000..e16f863 --- /dev/null +++ b/backend/src/core/modules/auth/repository/index.js @@ -0,0 +1,3 @@ +export * from './create-refresh-token.repository'; +export * from './forgot-password.repository'; +export * from './password-reset.repository'; \ No newline at end of file diff --git a/backend/src/core/modules/auth/service/auth.service.js b/backend/src/core/modules/auth/service/auth.service.js index 277643c..d45b666 100644 --- a/backend/src/core/modules/auth/service/auth.service.js +++ b/backend/src/core/modules/auth/service/auth.service.js @@ -20,7 +20,7 @@ class Service { this.userRepository = UserRepository; this.jwtService = JwtService; this.bcryptService = BcryptService; - this.refreshTokenRepository = RefreshTokenRepository; + this.createRefreshTokenRepository = CreateRefreshTokenRepository; this.passwordResetRepository = PasswordResetRepository; this.forgotPasswordRepository = ForgotPasswordRepository; } @@ -303,7 +303,7 @@ class Service { async #createRefreshToken(userId) { const token = crypto.randomBytes(32).toString('hex'); const expiresAt = new Date(Date.now() + REFRESH_TOKEN_EXPIRY); - const record = await this.refreshTokenRepository.createToken(userId, token, expiresAt); + const record = await this.createRefreshTokenRepository.createToken(userId, token, expiresAt); return { id: record.user_id || record, token: record.token || token From 63284b4639954d4be3ddd72337543d361783b5f2 Mon Sep 17 00:00:00 2001 From: DQGiac Date: Wed, 20 May 2026 10:09:15 +0700 Subject: [PATCH 17/26] feat[FI-12]: Implemented RBAC middleware and auth guard. Cleaned up code. --- backend/src/core/api/auth/auth.resolver.js | 16 -------- .../dto/{login-user.dto.js => login.dto.js} | 4 +- backend/src/core/modules/auth/index.js | 2 +- .../auth/interceptor/login.interceptor.js | 2 +- .../update-my-profile.interceptor.js | 2 +- .../create-refresh-token.repository.js | 24 ------------ .../src/core/modules/auth/repository/index.js | 3 -- .../core/modules/auth/service/auth.service.js | 37 ++----------------- backend/src/core/rules.js | 4 +- 9 files changed, 10 insertions(+), 84 deletions(-) rename backend/src/core/modules/auth/dto/{login-user.dto.js => login.dto.js} (88%) delete mode 100644 backend/src/core/modules/auth/repository/create-refresh-token.repository.js delete mode 100644 backend/src/core/modules/auth/repository/index.js diff --git a/backend/src/core/api/auth/auth.resolver.js b/backend/src/core/api/auth/auth.resolver.js index 1b72783..324b65e 100644 --- a/backend/src/core/api/auth/auth.resolver.js +++ b/backend/src/core/api/auth/auth.resolver.js @@ -54,22 +54,6 @@ export const AuthResolver = Module.builder() controller: AuthController.updateMyProfile, preAuthorization: true, }, - { - route: '/me/:id', - method: 'get', - params: [GetMyProfileParams], - interceptors: [GetMyProfileInterceptor], - controller: AuthController.getMyProfile, - preAuthorization: true, - }, - { - route: '/update_my_profile', - method: 'post', - interceptors: [UpdateMyProfileInterceptor], - body: 'UpdateMyProfileDto', - controller: AuthController.updateMyProfile, - preAuthorization: true, - }, { route: '/forgot_password', method: 'post', diff --git a/backend/src/core/modules/auth/dto/login-user.dto.js b/backend/src/core/modules/auth/dto/login.dto.js similarity index 88% rename from backend/src/core/modules/auth/dto/login-user.dto.js rename to backend/src/core/modules/auth/dto/login.dto.js index 8dd724d..1acabf0 100644 --- a/backend/src/core/modules/auth/dto/login-user.dto.js +++ b/backend/src/core/modules/auth/dto/login.dto.js @@ -6,7 +6,7 @@ ApiDocument.addModel('LoginDto', { password: SwaggerDocument.ApiProperty({ type: 'string' }), }); -export const LoginUserDto = body => ({ +export const LoginDto = body => ({ email: body.email, password: body.password, -}); \ No newline at end of file +}); diff --git a/backend/src/core/modules/auth/index.js b/backend/src/core/modules/auth/index.js index c7aec32..646f1aa 100644 --- a/backend/src/core/modules/auth/index.js +++ b/backend/src/core/modules/auth/index.js @@ -1,3 +1,3 @@ export * from './dto'; export * from './service'; -export * from './interceptor'; \ No newline at end of file +export * from './interceptor'; diff --git a/backend/src/core/modules/auth/interceptor/login.interceptor.js b/backend/src/core/modules/auth/interceptor/login.interceptor.js index 6a8917a..6421fd7 100644 --- a/backend/src/core/modules/auth/interceptor/login.interceptor.js +++ b/backend/src/core/modules/auth/interceptor/login.interceptor.js @@ -2,7 +2,7 @@ import { DefaultValidatorInterceptor } from 'core/infrastructure/interceptor'; import { JoiUtils } from 'core/utils'; import Joi from 'joi'; -export const LoginUserInterceptor = new DefaultValidatorInterceptor( +export const LoginInterceptor = new DefaultValidatorInterceptor( Joi.object({ email: JoiUtils.email().required().pattern(/^.*@.*\.(com|net|org)$/).message({ 'string.pattern.base': 'Email must be a valid email address.' diff --git a/backend/src/core/modules/auth/interceptor/update-my-profile.interceptor.js b/backend/src/core/modules/auth/interceptor/update-my-profile.interceptor.js index fb43108..1f26f98 100644 --- a/backend/src/core/modules/auth/interceptor/update-my-profile.interceptor.js +++ b/backend/src/core/modules/auth/interceptor/update-my-profile.interceptor.js @@ -7,7 +7,7 @@ export const UpdateMyProfileInterceptor = new DefaultValidatorInterceptor( avatar_url: JoiUtils.optionalString().allow(''), full_name: JoiUtils.optionalString().allow(''), phone: JoiUtils.optionalString().allow(''), - date_of_birth: JoiUtils.optionalString().allow('').pattern(/^\d{4}-\d{2}-\d{2}$/), + date_of_birth: JoiUtils.optionalString().empty(''), location: JoiUtils.optionalString().allow(''), }) ); \ No newline at end of file diff --git a/backend/src/core/modules/auth/repository/create-refresh-token.repository.js b/backend/src/core/modules/auth/repository/create-refresh-token.repository.js deleted file mode 100644 index c5d30d9..0000000 --- a/backend/src/core/modules/auth/repository/create-refresh-token.repository.js +++ /dev/null @@ -1,24 +0,0 @@ -import { DataRepository } from 'packages/restBuilder/core/dataHandler/data.repository'; -import connection from 'core/database'; - -class Repository extends DataRepository { - async createToken(userId, token, expiresAt, trx = null) { - return await connection.refresh_tokens.upsert({ - where: { - user_id: userId, - }, - update: { - token, - expires_at: expiresAt, - }, - create: { - user_id: userId, - token, - expires_at: expiresAt, - }, - select: { user_id: true, token: true }, - }); - } -} - -export const CreateRefreshTokenRepository = new Repository('refresh_tokens'); \ No newline at end of file diff --git a/backend/src/core/modules/auth/repository/index.js b/backend/src/core/modules/auth/repository/index.js deleted file mode 100644 index e16f863..0000000 --- a/backend/src/core/modules/auth/repository/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export * from './create-refresh-token.repository'; -export * from './forgot-password.repository'; -export * from './password-reset.repository'; \ No newline at end of file diff --git a/backend/src/core/modules/auth/service/auth.service.js b/backend/src/core/modules/auth/service/auth.service.js index d45b666..51ef762 100644 --- a/backend/src/core/modules/auth/service/auth.service.js +++ b/backend/src/core/modules/auth/service/auth.service.js @@ -20,9 +20,6 @@ class Service { this.userRepository = UserRepository; this.jwtService = JwtService; this.bcryptService = BcryptService; - this.createRefreshTokenRepository = CreateRefreshTokenRepository; - this.passwordResetRepository = PasswordResetRepository; - this.forgotPasswordRepository = ForgotPasswordRepository; } async register(registerDto) { @@ -77,16 +74,6 @@ class Service { throw new UnAuthorizedException('Email or password is incorrect'); } - if (loginDto.role === "LAWYER") { - let existingLicenseNumber = await connection.lawyer_details.findFirst({ - where: {user_id: user.id} - }); - - if (existingLicenseNumber.license_number !== loginDto.license_number) { - throw new UnAuthorizedException('Invalid credentials') - } - } - const userData = { user_id: user.id, email: user.email, @@ -205,7 +192,9 @@ class Service { avatar_url: updateMyProfileDto.avatar_url, full_name: updateMyProfileDto.full_name, phone: updateMyProfileDto.phone, - date_of_birth: new Date(updateMyProfileDto.date_of_birth).toISOString(), + date_of_birth: updateMyProfileDto.date_of_birth + ? new Date(updateMyProfileDto.date_of_birth).toISOString() + : null, location: updateMyProfileDto.location, }, select: { @@ -299,26 +288,6 @@ class Service { token: record.token || token }; } - - async #createRefreshToken(userId) { - const token = crypto.randomBytes(32).toString('hex'); - const expiresAt = new Date(Date.now() + REFRESH_TOKEN_EXPIRY); - const record = await this.createRefreshTokenRepository.createToken(userId, token, expiresAt); - return { - id: record.user_id || record, - token: record.token || token - }; - } - - async #createPasswordResetToken(userId) { - const token = crypto.randomBytes(32).toString('hex'); - const expiresAt = new Date(Date.now() + PASSWORD_RESET_TOKEN_EXPIRY); - const record = await this.passwordResetRepository.passwordResetCreateToken(userId, token, expiresAt); - return { - id: record.user_id || record, - token: record.token || token - }; - } } export const AuthService = new Service(); diff --git a/backend/src/core/rules.js b/backend/src/core/rules.js index c9d909e..8ad3120 100644 --- a/backend/src/core/rules.js +++ b/backend/src/core/rules.js @@ -13,10 +13,10 @@ export const Role = { }, LAWYER: { name: 'LAWYER', - // permissions: ['TEST_AUTHORIZATION'] + permissions: ['TEST_AUTHORIZATION'] }, USER: { name: 'USER', - // permissions: ['TEST_AUTHORIZATION'] + permissions: ['TEST_AUTHORIZATION'] } }; From 97d06ec1bb33a7be380b36144fa3b3dd17984988 Mon Sep 17 00:00:00 2001 From: DQGiac Date: Fri, 5 Jun 2026 22:56:48 +0700 Subject: [PATCH 18/26] feat[FI-12]: Log out --- backend/src/core/api/auth/auth.controller.js | 4 ++++ backend/src/core/api/auth/auth.resolver.js | 10 ++++++++++ .../src/core/modules/auth/auth.repository.js | 12 ++++++++++++ backend/src/core/modules/auth/dto/index.js | 1 + .../src/core/modules/auth/dto/logout.dto.js | 10 ++++++++++ .../core/modules/auth/interceptor/index.js | 1 + .../auth/interceptor/logout.interceptor.js | 10 ++++++++++ .../core/modules/auth/service/auth.service.js | 19 ++++++++++++++++++- 8 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 backend/src/core/modules/auth/dto/logout.dto.js create mode 100644 backend/src/core/modules/auth/interceptor/logout.interceptor.js diff --git a/backend/src/core/api/auth/auth.controller.js b/backend/src/core/api/auth/auth.controller.js index edc7a22..3886693 100644 --- a/backend/src/core/api/auth/auth.controller.js +++ b/backend/src/core/api/auth/auth.controller.js @@ -44,6 +44,10 @@ class Controller { const data = await this.service.refreshToken(RefreshTokenDto(req.body), req.user.payload.id); return ValidHttpResponse.toOkResponse(data); } + + logout = async req => { + const data = await this.service.logout() + } } export const AuthController = new Controller(); diff --git a/backend/src/core/api/auth/auth.resolver.js b/backend/src/core/api/auth/auth.resolver.js index 73b5b52..fa69ce1 100644 --- a/backend/src/core/api/auth/auth.resolver.js +++ b/backend/src/core/api/auth/auth.resolver.js @@ -7,6 +7,7 @@ import { VerifyOtpInterceptor, ResetPasswordInterceptor, RefreshTokenInterceptor, + LogoutInterceptor } from 'core/modules/auth'; import { Module } from 'packages/handler/Module'; import { AuthController } from './auth.controller'; @@ -82,4 +83,13 @@ export const AuthResolver = Module.builder() controller: AuthController.refreshToken, preAuthorization: true, }, + { + route: '/logout', + method: 'post', + interceptors: [LogoutInterceptor], + middleware: [authMiddleware], + body: 'LogoutDto', + controller: AuthController.logout, + preAuthorization: true, + }, ]); diff --git a/backend/src/core/modules/auth/auth.repository.js b/backend/src/core/modules/auth/auth.repository.js index 7d3f786..6d75b85 100644 --- a/backend/src/core/modules/auth/auth.repository.js +++ b/backend/src/core/modules/auth/auth.repository.js @@ -103,6 +103,14 @@ class Repository { }); } + async deleteRefreshToken(token) { + return connection.refresh_tokens.update({ + where: { token }, + data: { deleted_at: Date.now() }, + select: { user_id: true, deleted_at: true } + }); + } + async upsertRefreshToken(userId, token, expiresAt) { return connection.refresh_tokens.upsert({ where: { user_id: userId }, @@ -111,6 +119,10 @@ class Repository { select: { user_id: true, token: true }, }); } + + async deleteRefreshToken(token) { + + } } export const AuthRepository = new Repository(); diff --git a/backend/src/core/modules/auth/dto/index.js b/backend/src/core/modules/auth/dto/index.js index ed0d654..3087b1e 100644 --- a/backend/src/core/modules/auth/dto/index.js +++ b/backend/src/core/modules/auth/dto/index.js @@ -6,3 +6,4 @@ export * from './verify-otp.dto'; export * from './reset-password.dto'; export * from './update-my-profile.dto'; export * from './refresh-token.dto'; +export * from './logout.dto'; diff --git a/backend/src/core/modules/auth/dto/logout.dto.js b/backend/src/core/modules/auth/dto/logout.dto.js new file mode 100644 index 0000000..b4b71db --- /dev/null +++ b/backend/src/core/modules/auth/dto/logout.dto.js @@ -0,0 +1,10 @@ +import { ApiDocument } from 'core/config/swagger.config'; +import { SwaggerDocument } from 'packages/swagger'; + +ApiDocument.addModel('LogoutDto', { + refresh_token: SwaggerDocument.ApiProperty({ type: 'string' }), +}); + +export const LogoutDto = body => ({ + refresh_token: body.refresh_token, +}); \ No newline at end of file diff --git a/backend/src/core/modules/auth/interceptor/index.js b/backend/src/core/modules/auth/interceptor/index.js index 7320998..eca3b6d 100644 --- a/backend/src/core/modules/auth/interceptor/index.js +++ b/backend/src/core/modules/auth/interceptor/index.js @@ -6,3 +6,4 @@ export * from './forgot-password.interceptor'; export * from './verify-otp.interceptor'; export * from './reset-password.interceptor'; export * from './refresh-token.interceptor'; +export * from './logout.interceptor'; diff --git a/backend/src/core/modules/auth/interceptor/logout.interceptor.js b/backend/src/core/modules/auth/interceptor/logout.interceptor.js new file mode 100644 index 0000000..36afe90 --- /dev/null +++ b/backend/src/core/modules/auth/interceptor/logout.interceptor.js @@ -0,0 +1,10 @@ +import { DefaultValidatorInterceptor } from 'core/infrastructure/interceptor'; +import { JoiUtils } from 'core/utils'; +import Joi from 'joi'; + +export const LogoutInterceptor = new DefaultValidatorInterceptor( + Joi.object({ + refresh_token: JoiUtils.requiredString(), + }), + 'params' +); diff --git a/backend/src/core/modules/auth/service/auth.service.js b/backend/src/core/modules/auth/service/auth.service.js index 84581f6..5375c39 100644 --- a/backend/src/core/modules/auth/service/auth.service.js +++ b/backend/src/core/modules/auth/service/auth.service.js @@ -175,7 +175,7 @@ class Service { if (!result) { throw new UnAuthorizedException("Invalid or expired refresh token"); } - if (result.expires_at < Date.now()) { + if (result.expires_at < Date.now() || result.deleted_at) { throw new UnAuthorizedException("Invalid or expired refresh token"); } @@ -190,6 +190,23 @@ class Service { } } + async logout(logoutDto) { + const existToken = await this.repository.findRefreshToken(logoutDto.refresh_token); + if (!existToken) { + throw new UnAuthorizedException("Invalid or expired refresh token"); + } + + if (existToken.expires_at < Date.now()) { + throw new UnAuthorizedException("Invalid or expired refresh token"); + } + + await this.repository.deleteRefreshToken(logoutDto.refresh_token); + return { + type: "string", + message: "Logout successful ", + }; + } + async #createRefreshToken(userId) { const token = crypto.randomBytes(32).toString('hex'); const expiresAt = new Date(Date.now() + REFRESH_TOKEN_EXPIRY); From a0a5fd931885cf5788819bc8f25ef4d8435b9f47 Mon Sep 17 00:00:00 2001 From: DQGiac Date: Sat, 6 Jun 2026 16:26:15 +0700 Subject: [PATCH 19/26] feat[FI-36]: Log out API --- backend/src/core/modules/auth/interceptor/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/core/modules/auth/interceptor/index.js b/backend/src/core/modules/auth/interceptor/index.js index eca3b6d..d23b681 100644 --- a/backend/src/core/modules/auth/interceptor/index.js +++ b/backend/src/core/modules/auth/interceptor/index.js @@ -6,4 +6,4 @@ export * from './forgot-password.interceptor'; export * from './verify-otp.interceptor'; export * from './reset-password.interceptor'; export * from './refresh-token.interceptor'; -export * from './logout.interceptor'; +export * from './logout.interceptor'; \ No newline at end of file From cca1f3bb74459bd29feaf3c42574b374a0be064a Mon Sep 17 00:00:00 2001 From: DQGiac Date: Sat, 6 Jun 2026 17:28:01 +0700 Subject: [PATCH 20/26] feat[FI-36]: Fix log out --- backend/package-lock.json | 4 ---- backend/src/core/modules/auth/auth.repository.js | 4 ---- 2 files changed, 8 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 402da01..f51649e 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -29,10 +29,6 @@ "lodash": "^4.17.21", "method-override": "^3.0.0", "multer": "^1.4.3", -<<<<<<< HEAD - "nodemailer": "^6.10.1", -======= ->>>>>>> f09c63d76d8f5ac39c031297ec99db7e2d691508 "pg": "^8.7.1", "prettier": "^2.3.0", "resend": "^3.5.0", diff --git a/backend/src/core/modules/auth/auth.repository.js b/backend/src/core/modules/auth/auth.repository.js index 291f02d..4896184 100644 --- a/backend/src/core/modules/auth/auth.repository.js +++ b/backend/src/core/modules/auth/auth.repository.js @@ -154,10 +154,6 @@ class Repository { select: { user_id: true, token: true }, }); } - - async deleteRefreshToken(token) { - - } } export const AuthRepository = new Repository(); From e92614b39264067a353b896ccacdc193ae927a83 Mon Sep 17 00:00:00 2001 From: DQGiac Date: Sat, 6 Jun 2026 17:31:24 +0700 Subject: [PATCH 21/26] feat[FI-36]: fix log out (2) --- backend/package-lock.json | 50 +++++++++++++-------------------------- 1 file changed, 16 insertions(+), 34 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index f51649e..3569d7d 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -118,7 +118,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.12.tgz", "integrity": "sha512-dK5PtG1uiN2ikk++5OzSYsitZKny4wOCD0nrO4TqnW4BVBTQ2NGS3NgilvT/TEyxTST7LNyWV/T4tXDoD3fOgg==", "dev": true, - "peer": true, "dependencies": { "@babel/code-frame": "^7.16.7", "@babel/generator": "^7.16.8", @@ -2610,8 +2609,7 @@ "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.4.1.tgz", "integrity": "sha512-mZ9NzzUSYPOCnxHH1oAHPRzoMFJHY472raDKwXl/+6oPbpdJ7g8LsCN4FSaIIfkiCKHhb3iF/Zqo3NYxaIhU7Q==", "devOptional": true, - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/@electric-sql/pglite-socket": { "version": "0.1.1", @@ -3477,7 +3475,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.19.0" } @@ -3531,7 +3528,6 @@ "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4969,7 +4965,8 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "devOptional": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/dargs": { "version": "7.0.0", @@ -5653,7 +5650,6 @@ "version": "7.32.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", - "peer": true, "dependencies": { "@babel/code-frame": "7.12.11", "@eslint/eslintrc": "^0.4.3", @@ -5781,7 +5777,6 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz", "integrity": "sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA==", "dev": true, - "peer": true, "dependencies": { "array-includes": "^3.1.4", "array.prototype.flat": "^1.2.5", @@ -7010,7 +7005,6 @@ "integrity": "sha512-jN0ZewiNAWSe5khM3EyCmBb250+b40wWbwNILNfEvq84VREWwOIkuUsFONk/3i3nqkz7Oe1PcpM2mwQEK2L9Kg==", "devOptional": true, "license": "MIT", - "peer": true, "engines": { "node": ">=16.9.0" } @@ -8102,8 +8096,7 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "peer": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash.camelcase": { "version": "4.3.0", @@ -9715,7 +9708,6 @@ "version": "2.5.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", - "peer": true, "bin": { "prettier": "bin-prettier.js" }, @@ -9741,7 +9733,6 @@ "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@prisma/config": "7.8.0", "@prisma/dev": "0.24.3", @@ -10480,7 +10471,8 @@ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "devOptional": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/selderee": { "version": "0.11.0", @@ -11270,7 +11262,6 @@ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", "dev": true, - "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -12008,7 +11999,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.12.tgz", "integrity": "sha512-dK5PtG1uiN2ikk++5OzSYsitZKny4wOCD0nrO4TqnW4BVBTQ2NGS3NgilvT/TEyxTST7LNyWV/T4tXDoD3fOgg==", "dev": true, - "peer": true, "requires": { "@babel/code-frame": "^7.16.7", "@babel/generator": "^7.16.8", @@ -13771,8 +13761,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.4.1.tgz", "integrity": "sha512-mZ9NzzUSYPOCnxHH1oAHPRzoMFJHY472raDKwXl/+6oPbpdJ7g8LsCN4FSaIIfkiCKHhb3iF/Zqo3NYxaIhU7Q==", - "devOptional": true, - "peer": true + "devOptional": true }, "@electric-sql/pglite-socket": { "version": "0.1.1", @@ -14390,7 +14379,6 @@ "version": "25.6.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", - "peer": true, "requires": { "undici-types": "~7.19.0" } @@ -14438,8 +14426,7 @@ "acorn": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "peer": true + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" }, "acorn-jsx": { "version": "5.3.2", @@ -15482,7 +15469,8 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "devOptional": true + "devOptional": true, + "peer": true }, "dargs": { "version": "7.0.0", @@ -15966,7 +15954,6 @@ "version": "7.32.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", - "peer": true, "requires": { "@babel/code-frame": "7.12.11", "@eslint/eslintrc": "^0.4.3", @@ -16166,7 +16153,6 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz", "integrity": "sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA==", "dev": true, - "peer": true, "requires": { "array-includes": "^3.1.4", "array.prototype.flat": "^1.2.5", @@ -16939,8 +16925,7 @@ "version": "4.12.16", "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.16.tgz", "integrity": "sha512-jN0ZewiNAWSe5khM3EyCmBb250+b40wWbwNILNfEvq84VREWwOIkuUsFONk/3i3nqkz7Oe1PcpM2mwQEK2L9Kg==", - "devOptional": true, - "peer": true + "devOptional": true }, "hosted-git-info": { "version": "4.1.0", @@ -17711,8 +17696,7 @@ "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "peer": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash.camelcase": { "version": "4.3.0", @@ -18895,8 +18879,7 @@ "prettier": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", - "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", - "peer": true + "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==" }, "prettier-linter-helpers": { "version": "1.0.0", @@ -18911,7 +18894,6 @@ "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.8.0.tgz", "integrity": "sha512-yfN4yrw7HV9kEJhoy1+jgah0jafEIQsf7uWouSsM8MvJtlubsk+kM7AIBWZ8+GJl74Yj3c+nbYqBkMOxtsZ3Lw==", "devOptional": true, - "peer": true, "requires": { "@prisma/config": "7.8.0", "@prisma/dev": "0.24.3", @@ -19456,7 +19438,8 @@ "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "devOptional": true + "devOptional": true, + "peer": true }, "selderee": { "version": "0.11.0", @@ -20058,7 +20041,6 @@ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", "dev": true, - "peer": true, "requires": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -20554,4 +20536,4 @@ } } } -} +} \ No newline at end of file From 6e2fa03607560dd6a43577339d455d34ba935768 Mon Sep 17 00:00:00 2001 From: DQGiac Date: Sat, 6 Jun 2026 17:33:45 +0700 Subject: [PATCH 22/26] feat[FI-36]: fix log out (3) --- backend/yarn.lock | 8 -------- 1 file changed, 8 deletions(-) diff --git a/backend/yarn.lock b/backend/yarn.lock index d7cf25d..8194558 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -4675,14 +4675,6 @@ node-releases@^2.0.1: resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz" integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA== -<<<<<<< HEAD -nodemailer@^6.10.1: - version "6.10.1" - resolved "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.1.tgz" - integrity sha512-Z+iLaBGVaSjbIzQ4pX6XV41HrooLsQ10ZWPUehGmuantvzWoDVBnmsdUcOIDM1t+yPor5pDhVlDESgOMEGxhHA== - -======= ->>>>>>> f09c63d76d8f5ac39c031297ec99db7e2d691508 nodemon@^2.0.22: version "2.0.22" resolved "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz" From 97bf55ebb1679b55834ec666acfc128c47e9b71e Mon Sep 17 00:00:00 2001 From: DQGiac Date: Tue, 9 Jun 2026 23:47:42 +0700 Subject: [PATCH 23/26] feat[FI-36]: fix log out (4) --- backend/src/core/modules/auth/service/auth.service.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/backend/src/core/modules/auth/service/auth.service.js b/backend/src/core/modules/auth/service/auth.service.js index 27e39f6..d6cbdd8 100644 --- a/backend/src/core/modules/auth/service/auth.service.js +++ b/backend/src/core/modules/auth/service/auth.service.js @@ -246,10 +246,6 @@ class Service { throw new UnAuthorizedException("Invalid or expired refresh token"); } - if (existToken.expires_at < Date.now()) { - throw new UnAuthorizedException("Invalid or expired refresh token"); - } - await this.repository.revokeRefreshToken(logoutDto.refresh_token); return { type: "string", From 4eeb5fe6100e9c3d384b3e10a4c40611cfebfa3d Mon Sep 17 00:00:00 2001 From: DQGiac Date: Sat, 20 Jun 2026 01:48:06 +0700 Subject: [PATCH 24/26] feat/FI-114: get reports stats --- .vscode/c_cpp_properties.json | 20 +++++ backend/package-lock.json | 54 ++++++++----- backend/package.json | 2 +- backend/src/core/api/index.js | 4 +- .../src/core/api/lawyer/lawyer.resolver.js | 2 +- .../core/api/reports/reports.controller.js | 20 +++++ .../src/core/api/reports/reports.resolver.js | 35 +++++++++ backend/src/core/api/user/user.resolver.js | 2 +- backend/src/core/common/swagger/index.js | 2 +- .../swagger/{filter.js => lawyerFilter.js} | 0 .../src/core/common/swagger/reportFilter.js | 52 +++++++++++++ .../modules/reports/dto/getReports.dto.js | 18 +++++ .../reports/dto/getReportsStats.dto.js | 10 +++ backend/src/core/modules/reports/dto/index.js | 2 + backend/src/core/modules/reports/index.js | 3 + .../interceptor/getReports.interceptor.js | 13 ++++ .../getReportsStats.interceptor.js | 11 +++ .../core/modules/reports/interceptor/index.js | 2 + .../modules/reports/reports.repository.js | 40 ++++++++++ .../src/core/modules/reports/service/index.js | 1 + .../reports/service/reports.service.js | 75 +++++++++++++++++++ 21 files changed, 343 insertions(+), 25 deletions(-) create mode 100644 .vscode/c_cpp_properties.json create mode 100644 backend/src/core/api/reports/reports.controller.js create mode 100644 backend/src/core/api/reports/reports.resolver.js rename backend/src/core/common/swagger/{filter.js => lawyerFilter.js} (100%) create mode 100644 backend/src/core/common/swagger/reportFilter.js create mode 100644 backend/src/core/modules/reports/dto/getReports.dto.js create mode 100644 backend/src/core/modules/reports/dto/getReportsStats.dto.js create mode 100644 backend/src/core/modules/reports/dto/index.js create mode 100644 backend/src/core/modules/reports/index.js create mode 100644 backend/src/core/modules/reports/interceptor/getReports.interceptor.js create mode 100644 backend/src/core/modules/reports/interceptor/getReportsStats.interceptor.js create mode 100644 backend/src/core/modules/reports/interceptor/index.js create mode 100644 backend/src/core/modules/reports/reports.repository.js create mode 100644 backend/src/core/modules/reports/service/index.js create mode 100644 backend/src/core/modules/reports/service/reports.service.js diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..9e49cb4 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,20 @@ +{ + "configurations": [ + { + "name": "Win32", + "includePath": [ + "${workspaceFolder}/**" + ], + "defines": [ + "_DEBUG", + "UNICODE", + "_UNICODE" + ], + "compilerPath": "C:\\msys64\\ucrt64\\bin\\gcc.exe", + "cStandard": "c23", + "cppStandard": "gnu++17", + "intelliSenseMode": "windows-gcc-x64" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/backend/package-lock.json b/backend/package-lock.json index 3569d7d..73f21e2 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -118,6 +118,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.12.tgz", "integrity": "sha512-dK5PtG1uiN2ikk++5OzSYsitZKny4wOCD0nrO4TqnW4BVBTQ2NGS3NgilvT/TEyxTST7LNyWV/T4tXDoD3fOgg==", "dev": true, + "peer": true, "dependencies": { "@babel/code-frame": "^7.16.7", "@babel/generator": "^7.16.8", @@ -2609,7 +2610,8 @@ "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.4.1.tgz", "integrity": "sha512-mZ9NzzUSYPOCnxHH1oAHPRzoMFJHY472raDKwXl/+6oPbpdJ7g8LsCN4FSaIIfkiCKHhb3iF/Zqo3NYxaIhU7Q==", "devOptional": true, - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/@electric-sql/pglite-socket": { "version": "0.1.1", @@ -3475,6 +3477,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.19.0" } @@ -3528,6 +3531,7 @@ "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4965,8 +4969,7 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "devOptional": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/dargs": { "version": "7.0.0", @@ -5650,6 +5653,7 @@ "version": "7.32.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "peer": true, "dependencies": { "@babel/code-frame": "7.12.11", "@eslint/eslintrc": "^0.4.3", @@ -5777,6 +5781,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz", "integrity": "sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA==", "dev": true, + "peer": true, "dependencies": { "array-includes": "^3.1.4", "array.prototype.flat": "^1.2.5", @@ -7005,6 +7010,7 @@ "integrity": "sha512-jN0ZewiNAWSe5khM3EyCmBb250+b40wWbwNILNfEvq84VREWwOIkuUsFONk/3i3nqkz7Oe1PcpM2mwQEK2L9Kg==", "devOptional": true, "license": "MIT", + "peer": true, "engines": { "node": ">=16.9.0" } @@ -8096,7 +8102,8 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "peer": true }, "node_modules/lodash.camelcase": { "version": "4.3.0", @@ -8347,7 +8354,6 @@ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "license": "MIT", - "peer": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -9708,6 +9714,7 @@ "version": "2.5.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", + "peer": true, "bin": { "prettier": "bin-prettier.js" }, @@ -9733,6 +9740,7 @@ "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@prisma/config": "7.8.0", "@prisma/dev": "0.24.3", @@ -10327,7 +10335,6 @@ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" } @@ -10471,8 +10478,7 @@ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "devOptional": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/selderee": { "version": "0.11.0", @@ -11262,6 +11268,7 @@ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", "dev": true, + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -11999,6 +12006,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.12.tgz", "integrity": "sha512-dK5PtG1uiN2ikk++5OzSYsitZKny4wOCD0nrO4TqnW4BVBTQ2NGS3NgilvT/TEyxTST7LNyWV/T4tXDoD3fOgg==", "dev": true, + "peer": true, "requires": { "@babel/code-frame": "^7.16.7", "@babel/generator": "^7.16.8", @@ -13761,7 +13769,8 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.4.1.tgz", "integrity": "sha512-mZ9NzzUSYPOCnxHH1oAHPRzoMFJHY472raDKwXl/+6oPbpdJ7g8LsCN4FSaIIfkiCKHhb3iF/Zqo3NYxaIhU7Q==", - "devOptional": true + "devOptional": true, + "peer": true }, "@electric-sql/pglite-socket": { "version": "0.1.1", @@ -14379,6 +14388,7 @@ "version": "25.6.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", + "peer": true, "requires": { "undici-types": "~7.19.0" } @@ -14426,7 +14436,8 @@ "acorn": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "peer": true }, "acorn-jsx": { "version": "5.3.2", @@ -15469,8 +15480,7 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "devOptional": true, - "peer": true + "devOptional": true }, "dargs": { "version": "7.0.0", @@ -15954,6 +15964,7 @@ "version": "7.32.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "peer": true, "requires": { "@babel/code-frame": "7.12.11", "@eslint/eslintrc": "^0.4.3", @@ -16153,6 +16164,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz", "integrity": "sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA==", "dev": true, + "peer": true, "requires": { "array-includes": "^3.1.4", "array.prototype.flat": "^1.2.5", @@ -16925,7 +16937,8 @@ "version": "4.12.16", "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.16.tgz", "integrity": "sha512-jN0ZewiNAWSe5khM3EyCmBb250+b40wWbwNILNfEvq84VREWwOIkuUsFONk/3i3nqkz7Oe1PcpM2mwQEK2L9Kg==", - "devOptional": true + "devOptional": true, + "peer": true }, "hosted-git-info": { "version": "4.1.0", @@ -17696,7 +17709,8 @@ "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "peer": true }, "lodash.camelcase": { "version": "4.3.0", @@ -17899,7 +17913,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "peer": true, "requires": { "js-tokens": "^3.0.0 || ^4.0.0" } @@ -18879,7 +18892,8 @@ "prettier": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", - "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==" + "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", + "peer": true }, "prettier-linter-helpers": { "version": "1.0.0", @@ -18894,6 +18908,7 @@ "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.8.0.tgz", "integrity": "sha512-yfN4yrw7HV9kEJhoy1+jgah0jafEIQsf7uWouSsM8MvJtlubsk+kM7AIBWZ8+GJl74Yj3c+nbYqBkMOxtsZ3Lw==", "devOptional": true, + "peer": true, "requires": { "@prisma/config": "7.8.0", "@prisma/dev": "0.24.3", @@ -19329,7 +19344,6 @@ "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "peer": true, "requires": { "loose-envify": "^1.1.0" } @@ -19438,8 +19452,7 @@ "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "devOptional": true, - "peer": true + "devOptional": true }, "selderee": { "version": "0.11.0", @@ -20041,6 +20054,7 @@ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", "dev": true, + "peer": true, "requires": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -20536,4 +20550,4 @@ } } } -} \ No newline at end of file +} diff --git a/backend/package.json b/backend/package.json index 32a0044..c80c8ea 100644 --- a/backend/package.json +++ b/backend/package.json @@ -43,9 +43,9 @@ "lodash": "^4.17.21", "method-override": "^3.0.0", "multer": "^1.4.3", - "resend": "^3.5.0", "pg": "^8.7.1", "prettier": "^2.3.0", + "resend": "^3.5.0", "swagger-ui-express": "^4.1.6", "winston": "^3.3.3" }, diff --git a/backend/src/core/api/index.js b/backend/src/core/api/index.js index e4bed65..bf24a5a 100644 --- a/backend/src/core/api/index.js +++ b/backend/src/core/api/index.js @@ -5,6 +5,7 @@ import { HandlerResolver } from '../../packages/handler/HandlerResolver'; import { LawyerResolver } from 'core/api/lawyer/lawyer.resolver'; import { AuthResolver } from './auth/auth.resolver'; import { UploadResolver } from 'core/api/upload'; +import { ReportsResolver } from './reports/reports.resolver'; export const ModuleResolver = HandlerResolver .builder() @@ -14,6 +15,7 @@ export const ModuleResolver = HandlerResolver UserResolver, MediaResolver, LawyerResolver, - UploadResolver + UploadResolver, + ReportsResolver ]); diff --git a/backend/src/core/api/lawyer/lawyer.resolver.js b/backend/src/core/api/lawyer/lawyer.resolver.js index b2bc084..d05e3f8 100644 --- a/backend/src/core/api/lawyer/lawyer.resolver.js +++ b/backend/src/core/api/lawyer/lawyer.resolver.js @@ -1,6 +1,6 @@ import { Module } from 'packages/handler/Module'; import { LawyerController } from './lawyer.controller'; -import { DefaultQueryCriteriaDocument } from '../../common/swagger/filter'; +import { DefaultQueryCriteriaDocument } from '../../common/swagger/lawyerFilter'; import { RecordId } from '../../common/swagger/record-id'; import { RecordIdInterceptor } from 'core/modules/interceptor/recordId/record-id.interceptor'; diff --git a/backend/src/core/api/reports/reports.controller.js b/backend/src/core/api/reports/reports.controller.js new file mode 100644 index 0000000..d876f2b --- /dev/null +++ b/backend/src/core/api/reports/reports.controller.js @@ -0,0 +1,20 @@ +import { ReportsService } from '../../modules/reports/service/reports.service'; +import { GetReportsDto } from '../../modules/reports'; +import { ValidHttpResponse } from '../../../packages/handler/response/validHttp.response'; + +class Controller { + constructor() { + this.service = ReportsService; + } + + getReports = async req => { + const data = await this.service.getReports(req.query); + return ValidHttpResponse.toOkResponse(data); + } + getReportsStats = async req => { + const data = await this.service.getReportsStats(req.query); + return ValidHttpResponse.toOkResponse(data); + } +} + +export const ReportsController = new Controller(); diff --git a/backend/src/core/api/reports/reports.resolver.js b/backend/src/core/api/reports/reports.resolver.js new file mode 100644 index 0000000..64d8d0f --- /dev/null +++ b/backend/src/core/api/reports/reports.resolver.js @@ -0,0 +1,35 @@ +import { + GetReportsInterceptor +} from 'core/modules/reports'; +import { Module } from 'packages/handler/Module'; +import { ReportsController } from './reports.controller'; +import { authMiddleware } from 'core/modules/auth/middleware/auth.middleware'; +import { uploadMediaSwagger } from 'core/common/swagger'; +import { MediaInterceptor } from 'core/modules/document'; +import { hasAdminRole } from 'core/modules/auth/guard'; +import { GetReportsParams, GetReportsStatsParams } from 'core/common/swagger/reportFilter'; + +export const ReportsResolver = Module.builder() + .addPrefix({ + prefixPath: '/reports', + tag: 'reports', + module: 'ReportsModule' + }) + .register([ + { + route: '/', + method: 'get', + params: GetReportsParams, + guards: [hasAdminRole], + controller: ReportsController.getReports, + preAuthorization: true, + }, + { + route: '/stats', + method: 'get', + params: GetReportsStatsParams, + guards: [hasAdminRole], + controller: ReportsController.getReportsStats, + preAuthorization: true, + }, + ]); diff --git a/backend/src/core/api/user/user.resolver.js b/backend/src/core/api/user/user.resolver.js index ca8380e..3580fea 100644 --- a/backend/src/core/api/user/user.resolver.js +++ b/backend/src/core/api/user/user.resolver.js @@ -10,7 +10,7 @@ import { hasAdminRole } from 'core/modules/auth/guard'; import { RecordIdInterceptor } from 'core/modules/interceptor/recordId/record-id.interceptor'; import { UserController } from './user.controller'; import { RecordId } from '../../common/swagger/record-id'; -import { DefaultQueryCriteriaDocument } from '../../common/swagger/filter'; +import { DefaultQueryCriteriaDocument } from '../../common/swagger/lawyerFilter'; export const UserResolver = Module.builder() .addPrefix({ diff --git a/backend/src/core/common/swagger/index.js b/backend/src/core/common/swagger/index.js index abbd86b..a53538c 100644 --- a/backend/src/core/common/swagger/index.js +++ b/backend/src/core/common/swagger/index.js @@ -1,3 +1,3 @@ -export * from './filter'; +export * from './lawyerFilter'; export * from './record-id'; export * from './upload-file'; diff --git a/backend/src/core/common/swagger/filter.js b/backend/src/core/common/swagger/lawyerFilter.js similarity index 100% rename from backend/src/core/common/swagger/filter.js rename to backend/src/core/common/swagger/lawyerFilter.js diff --git a/backend/src/core/common/swagger/reportFilter.js b/backend/src/core/common/swagger/reportFilter.js new file mode 100644 index 0000000..e59e547 --- /dev/null +++ b/backend/src/core/common/swagger/reportFilter.js @@ -0,0 +1,52 @@ +import { SwaggerDocument } from '../../../packages/swagger'; + +export const GetReportsCriteriaDocument = { + page: (desc = 'Max page: 100000. Default: 1') => SwaggerDocument.ApiParams({ + name: 'page', + paramsIn: 'query', + required: true, + type: 'int', + description: desc + }), + limit: (desc = 'Max size: 200. Default: 20') => SwaggerDocument.ApiParams({ + name: 'limit', + paramsIn: 'query', + required: true, + type: 'int', + description: desc + }), + status: (desc = 'Of three types: pending, processing, and resolved') => SwaggerDocument.ApiParams({ + name: 'status', + paramsIn: 'query', + required: true, + type: 'date', + description: desc + }), + startDate: desc => SwaggerDocument.ApiParams({ + name: 'startDate', + paramsIn: 'query', + required: true, + type: 'date', + description: desc + }), + endDate: desc => SwaggerDocument.ApiParams({ + name: 'endDate', + paramsIn: 'query', + required: true, + type: 'date', + description: desc + }) +}; + +export const GetReportsStatsCriteriaDocument = { + month: (desc = 'Get reports statistics of month. Format: YYYY-MM') => SwaggerDocument.ApiParams({ + name: 'month', + paramsIn: 'query', + required: true, + type: 'date', + description: desc + }) +}; + +export const GetReportsParams = [...Object.values(GetReportsCriteriaDocument).map(exec => exec())]; +export const GetReportsStatsParams = [...Object.values(GetReportsStatsCriteriaDocument).map(exec => exec())]; \ No newline at end of file diff --git a/backend/src/core/modules/reports/dto/getReports.dto.js b/backend/src/core/modules/reports/dto/getReports.dto.js new file mode 100644 index 0000000..3c59d3a --- /dev/null +++ b/backend/src/core/modules/reports/dto/getReports.dto.js @@ -0,0 +1,18 @@ +import { ApiDocument } from 'core/config/swagger.config'; +import { SwaggerDocument } from 'packages/swagger'; + +ApiDocument.addModel('GetReportsDto', { + page: SwaggerDocument.ApiProperty({ type: 'string' }), + limit: SwaggerDocument.ApiProperty({ type: 'string' }), + status: SwaggerDocument.ApiProperty({ type: 'string' }), + startDate: SwaggerDocument.ApiProperty({ type: 'string' }), + endDate: SwaggerDocument.ApiProperty({ type: 'string' }), +}); + +export const GetReportsDto = body => ({ + page: body.page, + limit: body.limit, + status: body.status, + startDate: body.startDate, + endDate: body.endDate, +}); \ No newline at end of file diff --git a/backend/src/core/modules/reports/dto/getReportsStats.dto.js b/backend/src/core/modules/reports/dto/getReportsStats.dto.js new file mode 100644 index 0000000..2378e56 --- /dev/null +++ b/backend/src/core/modules/reports/dto/getReportsStats.dto.js @@ -0,0 +1,10 @@ +import { ApiDocument } from 'core/config/swagger.config'; +import { SwaggerDocument } from 'packages/swagger'; + +ApiDocument.addModel('GetReportsStatsDto', { + month: SwaggerDocument.ApiProperty({ type: 'string' }) +}); + +export const GetReportsStatsDto = body => ({ + month: body.month +}); \ No newline at end of file diff --git a/backend/src/core/modules/reports/dto/index.js b/backend/src/core/modules/reports/dto/index.js new file mode 100644 index 0000000..5d65515 --- /dev/null +++ b/backend/src/core/modules/reports/dto/index.js @@ -0,0 +1,2 @@ +export * from './getReports.dto'; +export * from './getReportsStats.dto'; \ No newline at end of file diff --git a/backend/src/core/modules/reports/index.js b/backend/src/core/modules/reports/index.js new file mode 100644 index 0000000..cc5f8c8 --- /dev/null +++ b/backend/src/core/modules/reports/index.js @@ -0,0 +1,3 @@ +export * from './dto'; +export * from './interceptor'; +export * from './service'; \ No newline at end of file diff --git a/backend/src/core/modules/reports/interceptor/getReports.interceptor.js b/backend/src/core/modules/reports/interceptor/getReports.interceptor.js new file mode 100644 index 0000000..4060bd9 --- /dev/null +++ b/backend/src/core/modules/reports/interceptor/getReports.interceptor.js @@ -0,0 +1,13 @@ +import { DefaultValidatorInterceptor } from 'core/infrastructure/interceptor'; +import { JoiUtils } from 'core/utils'; +import Joi from 'joi'; + +export const GetReportsInterceptor = new DefaultValidatorInterceptor( + Joi.object({ + page: JoiUtils.positiveNumber().min(1).default(1).required(), + limit: JoiUtils.positiveNumber().min(1).max(100).default(20).required(), + status: JoiUtils.requiredString(), + startDate: JoiUtils.date(), + endDate: JoiUtils.date(), + }) +); \ No newline at end of file diff --git a/backend/src/core/modules/reports/interceptor/getReportsStats.interceptor.js b/backend/src/core/modules/reports/interceptor/getReportsStats.interceptor.js new file mode 100644 index 0000000..129ab96 --- /dev/null +++ b/backend/src/core/modules/reports/interceptor/getReportsStats.interceptor.js @@ -0,0 +1,11 @@ +import { DefaultValidatorInterceptor } from 'core/infrastructure/interceptor'; +import { JoiUtils } from 'core/utils'; +import Joi from 'joi'; + +const DATE_YYYY_MM_FORMAT = /^\d{4}-\d{2}$/; + +export const GetReportsStatsInterceptor = new DefaultValidatorInterceptor( + Joi.object({ + month: Joi.string().regex(DATE_YYYY_MM_FORMAT), + }) +); \ No newline at end of file diff --git a/backend/src/core/modules/reports/interceptor/index.js b/backend/src/core/modules/reports/interceptor/index.js new file mode 100644 index 0000000..330b988 --- /dev/null +++ b/backend/src/core/modules/reports/interceptor/index.js @@ -0,0 +1,2 @@ +export * from './getReports.interceptor'; +export * from './getReportsStats.interceptor'; \ No newline at end of file diff --git a/backend/src/core/modules/reports/reports.repository.js b/backend/src/core/modules/reports/reports.repository.js new file mode 100644 index 0000000..b16d2ce --- /dev/null +++ b/backend/src/core/modules/reports/reports.repository.js @@ -0,0 +1,40 @@ +import connection from 'core/database'; + +class Repository { + async getReportsByDate(startDate, endDate) { + return connection.reports.findMany({ + where: { + created_at: { + gte: startDate, + lte: endDate + }, + deleted_at: null, + }, + select: { + status: true, + target_user_id: true, + reporter_id: true, + reason: true, + } + }); + } + async getReportsByDateAndStatus(status, startDate, endDate) { + return connection.reports.findMany({ + where: { + status: status, + created_at: { + gte: startDate, + lte: endDate + }, + deleted_at: NullTypes, + }, + select: { + target_user_id: true, + reporter_id: true, + reason: true, + } + }); + } +} + +export const ReportsRepository = new Repository(); \ No newline at end of file diff --git a/backend/src/core/modules/reports/service/index.js b/backend/src/core/modules/reports/service/index.js new file mode 100644 index 0000000..9de9ea5 --- /dev/null +++ b/backend/src/core/modules/reports/service/index.js @@ -0,0 +1 @@ +export * from './reports.service'; \ No newline at end of file diff --git a/backend/src/core/modules/reports/service/reports.service.js b/backend/src/core/modules/reports/service/reports.service.js new file mode 100644 index 0000000..8926d6b --- /dev/null +++ b/backend/src/core/modules/reports/service/reports.service.js @@ -0,0 +1,75 @@ +import crypto from 'crypto'; +import { MailerService } from 'core/common/mailer.service'; +import { JwtPayload } from 'core/modules/auth/dto/jwt-sign.dto'; +import { ReportsRepository } from '../reports.repository'; +// import { BcryptService } from './bcrypt.service'; +// import { JwtService } from './jwt.service'; +import { UnAuthorizedException, DuplicateException, BadRequestException } from '../../../../packages/httpException'; +import { DefaultQueryCriteriaDocument } from 'core/common/swagger/reportFilter'; + +class Service { + constructor() { + this.repository = ReportsRepository; + } + + async getReports(getReportsDto) { + let allStatus = ["pending", "processing", "resolved"]; + if (allStatus.indexOf(getReportsDto.status) == -1) { + throw new BadRequestException("Invalid Status value"); + } + + let startDate = getReportsDto.startDate + ? new Date(getReportsDto.startDate).toISOString() + : null; + let endDate = getReportsDto.endDate + ? new Date(getReportsDto.endDate).toISOString() + : null; + + if (startDate > endDate) { + throw new BadRequestException("startDate must be before endDate"); + } + + let reports = await this.repository.getReportsByDateAndStatus(getReportsDto.status, startDate, endDate); + reports = reports.slice((getReportsDto.page - 1) * getReportsDto.limit, getReportsDto.page * getReportsDto.limit); + return { + data: { + items: { + reports: reports, + }, + pagination: { + page: getReportsDto.page, + limit: getReportsDto.limit, + total: reports.length, + totalPages: reports.length / getReportsDto.limit, + } + } + }; + } + + async getReportsStats(getReportsStatsDto) { + if (getReportsStatsDto.month.length == 0) getReportsStatsDto.month = new Date().now().toISOString().slice(0, 8); + let year = parseInt(getReportsStatsDto.month.slice(0, 5)); + let month = parseInt(getReportsStatsDto.month.slice(6)); + let startDate = new Date(year + '-' + month + "-01").toISOString(); + year += (month + 1) % 12; + month = (month + 1) % 12; + + let endDate = new Date(new Date(year + '-' + month + "-01") - 1); + + let reports = await this.repository.getReportsByDate(startDate, endDate); + let resolvedReports = 0; + for (let i = 0; i < reports.length; i ++) { + if (reports[i].status == "RESOLVED") resolvedReports ++; + } + + return { + data: { + month: getReportsStatsDto.month, + newReports: reports.length, + resolved: resolvedReports + } + }; + } +} + +export const ReportsService = new Service(); From 060c329a5aa6947eedcc71b23e9a25d8e8a7b379 Mon Sep 17 00:00:00 2001 From: DQGiac Date: Tue, 30 Jun 2026 16:39:26 +0700 Subject: [PATCH 25/26] feat/FI-113: Implement get report history --- backend/package-lock.json | 31 ++++++--------- backend/package.json | 4 +- .../core/api/reports/reports.controller.js | 10 +++-- .../src/core/api/reports/reports.resolver.js | 14 +++++-- .../src/core/common/swagger/reportFilter.js | 25 ++++++++++-- .../reports/dto/getReportsHistory.dto.js | 12 ++++++ backend/src/core/modules/reports/dto/index.js | 5 ++- .../{getReports.dto.js => listReports.dto.js} | 4 +- .../getReportsHistory.interceptor.js | 10 +++++ .../core/modules/reports/interceptor/index.js | 5 ++- ...erceptor.js => listReports.interceptor.js} | 2 +- .../modules/reports/reports.repository.js | 19 +++++++++ .../reports/service/reports.service.js | 39 +++++++++++++------ backend/yarn.lock | 12 +----- 14 files changed, 133 insertions(+), 59 deletions(-) create mode 100644 backend/src/core/modules/reports/dto/getReportsHistory.dto.js rename backend/src/core/modules/reports/dto/{getReports.dto.js => listReports.dto.js} (87%) create mode 100644 backend/src/core/modules/reports/interceptor/getReportsHistory.interceptor.js rename backend/src/core/modules/reports/interceptor/{getReports.interceptor.js => listReports.interceptor.js} (85%) diff --git a/backend/package-lock.json b/backend/package-lock.json index b51b2da..736c2e7 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -18,7 +18,6 @@ "cloudinary": "^1.27.1", "cors": "^2.8.5", "debug": "^4.2.0", - "dotenv": "^8.2.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "express": "^4.17.2", @@ -55,6 +54,7 @@ "babel-eslint": "^10.1.0", "babel-plugin-module-resolver": "^4.1.0", "babel-plugin-transform-runtime": "^6.23.0", + "dotenv": "^17.4.2", "eslint": "^7.3.1", "eslint-config-airbnb-base": "^14.2.0", "eslint-plugin-import": "^2.21.2", @@ -4572,19 +4572,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/c12/node_modules/dotenv": { - "version": "17.4.2", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", - "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", - "devOptional": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, "node_modules/c12/node_modules/readdirp": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", @@ -5594,7 +5581,8 @@ "version": "0.0.1312386", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz", "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==", - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "peer": true }, "node_modules/dicer": { "version": "0.2.5", @@ -5702,11 +5690,16 @@ } }, "node_modules/dotenv": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", - "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "devOptional": true, + "license": "BSD-2-Clause", "engines": { - "node": ">=10" + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" } }, "node_modules/duck": { diff --git a/backend/package.json b/backend/package.json index a2c6bf2..2ec3789 100644 --- a/backend/package.json +++ b/backend/package.json @@ -15,7 +15,7 @@ "lint:staged": "lint-staged", "dev": "npx nodemon --exec npx babel-node src/core/bin/www.js", "start": "npm run build && node ./dist/core/bin/www.js", - "clean": "rimraf dist && mkdir dist", + "clean": "rimraf dist && mkdir dist", "build:babel": "babel ./src --out-dir dist --copy-files", "build": "npm run clean && npm run build:babel", "db:migrate": "npx prisma migrate dev", @@ -32,7 +32,6 @@ "cloudinary": "^1.27.1", "cors": "^2.8.5", "debug": "^4.2.0", - "dotenv": "^8.2.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "express": "^4.17.2", @@ -69,6 +68,7 @@ "babel-eslint": "^10.1.0", "babel-plugin-module-resolver": "^4.1.0", "babel-plugin-transform-runtime": "^6.23.0", + "dotenv": "^17.4.2", "eslint": "^7.3.1", "eslint-config-airbnb-base": "^14.2.0", "eslint-plugin-import": "^2.21.2", diff --git a/backend/src/core/api/reports/reports.controller.js b/backend/src/core/api/reports/reports.controller.js index d876f2b..4c84b68 100644 --- a/backend/src/core/api/reports/reports.controller.js +++ b/backend/src/core/api/reports/reports.controller.js @@ -1,5 +1,5 @@ import { ReportsService } from '../../modules/reports/service/reports.service'; -import { GetReportsDto } from '../../modules/reports'; +import { ListReportsDto } from '../../modules/reports'; import { ValidHttpResponse } from '../../../packages/handler/response/validHttp.response'; class Controller { @@ -7,14 +7,18 @@ class Controller { this.service = ReportsService; } - getReports = async req => { - const data = await this.service.getReports(req.query); + listReports = async req => { + const data = await this.service.listReports(req.query); return ValidHttpResponse.toOkResponse(data); } getReportsStats = async req => { const data = await this.service.getReportsStats(req.query); return ValidHttpResponse.toOkResponse(data); } + getReportsHistory = async req => { + const data = await this.service.getReportsHistory(req.query); + return ValidHttpResponse.toOkResponse(data); + } } export const ReportsController = new Controller(); diff --git a/backend/src/core/api/reports/reports.resolver.js b/backend/src/core/api/reports/reports.resolver.js index 64d8d0f..da246ed 100644 --- a/backend/src/core/api/reports/reports.resolver.js +++ b/backend/src/core/api/reports/reports.resolver.js @@ -7,7 +7,7 @@ import { authMiddleware } from 'core/modules/auth/middleware/auth.middleware'; import { uploadMediaSwagger } from 'core/common/swagger'; import { MediaInterceptor } from 'core/modules/document'; import { hasAdminRole } from 'core/modules/auth/guard'; -import { GetReportsParams, GetReportsStatsParams } from 'core/common/swagger/reportFilter'; +import { ListReportsParams, GetReportsStatsParams, GetReportsHistoryParams } from 'core/common/swagger/reportFilter'; export const ReportsResolver = Module.builder() .addPrefix({ @@ -19,9 +19,9 @@ export const ReportsResolver = Module.builder() { route: '/', method: 'get', - params: GetReportsParams, + params: ListReportsParams, guards: [hasAdminRole], - controller: ReportsController.getReports, + controller: ReportsController.listReports, preAuthorization: true, }, { @@ -32,4 +32,12 @@ export const ReportsResolver = Module.builder() controller: ReportsController.getReportsStats, preAuthorization: true, }, + { + route: '/history', + method: 'get', + params: GetReportsHistoryParams, + guards: [hasAdminRole], + controller: ReportsController.getReportsHistory, + preAuthorization: true, + }, ]); diff --git a/backend/src/core/common/swagger/reportFilter.js b/backend/src/core/common/swagger/reportFilter.js index e59e547..e1efca1 100644 --- a/backend/src/core/common/swagger/reportFilter.js +++ b/backend/src/core/common/swagger/reportFilter.js @@ -1,6 +1,6 @@ import { SwaggerDocument } from '../../../packages/swagger'; -export const GetReportsCriteriaDocument = { +export const ListReportsCriteriaDocument = { page: (desc = 'Max page: 100000. Default: 1') => SwaggerDocument.ApiParams({ name: 'page', paramsIn: 'query', @@ -48,5 +48,24 @@ export const GetReportsStatsCriteriaDocument = { }) }; -export const GetReportsParams = [...Object.values(GetReportsCriteriaDocument).map(exec => exec())]; -export const GetReportsStatsParams = [...Object.values(GetReportsStatsCriteriaDocument).map(exec => exec())]; \ No newline at end of file +export const GetReportsHistoryCriteriaDocument = { + page: (desc = 'Page number (starts from 1)') => SwaggerDocument.ApiParams({ + name: 'page', + paramsIn: 'query', + required: true, + type: 'int', + description: desc + }), + limit: (desc = 'Number of reports per page (max 100)') => SwaggerDocument.ApiParams({ + name: 'limit', + paramsIn: 'query', + required: true, + type: 'int', + description: desc + }), +}; + + +export const ListReportsParams = [...Object.values(ListReportsCriteriaDocument).map(exec => exec())]; +export const GetReportsStatsParams = [...Object.values(GetReportsStatsCriteriaDocument).map(exec => exec())]; +export const GetReportsHistoryParams = [...Object.values(GetReportsHistoryCriteriaDocument).map(exec => exec())]; \ No newline at end of file diff --git a/backend/src/core/modules/reports/dto/getReportsHistory.dto.js b/backend/src/core/modules/reports/dto/getReportsHistory.dto.js new file mode 100644 index 0000000..b742932 --- /dev/null +++ b/backend/src/core/modules/reports/dto/getReportsHistory.dto.js @@ -0,0 +1,12 @@ +import { ApiDocument } from 'core/config/swagger.config'; +import { SwaggerDocument } from 'packages/swagger'; + +ApiDocument.addModel('GetReportsHistoryDto', { + page: SwaggerDocument.ApiProperty({ type: 'string' }), + limit: SwaggerDocument.ApiProperty({ type: 'string' }), +}); + +export const GetReportsHistoryDto = body => ({ + page: body.page, + limit: body.limit +}); \ No newline at end of file diff --git a/backend/src/core/modules/reports/dto/index.js b/backend/src/core/modules/reports/dto/index.js index 5d65515..52a017e 100644 --- a/backend/src/core/modules/reports/dto/index.js +++ b/backend/src/core/modules/reports/dto/index.js @@ -1,2 +1,3 @@ -export * from './getReports.dto'; -export * from './getReportsStats.dto'; \ No newline at end of file +export * from './listReports.dto'; +export * from './getReportsStats.dto'; +export * from './getReportsHistory.dto'; \ No newline at end of file diff --git a/backend/src/core/modules/reports/dto/getReports.dto.js b/backend/src/core/modules/reports/dto/listReports.dto.js similarity index 87% rename from backend/src/core/modules/reports/dto/getReports.dto.js rename to backend/src/core/modules/reports/dto/listReports.dto.js index 3c59d3a..ff2d193 100644 --- a/backend/src/core/modules/reports/dto/getReports.dto.js +++ b/backend/src/core/modules/reports/dto/listReports.dto.js @@ -1,7 +1,7 @@ import { ApiDocument } from 'core/config/swagger.config'; import { SwaggerDocument } from 'packages/swagger'; -ApiDocument.addModel('GetReportsDto', { +ApiDocument.addModel('ListReportsDto', { page: SwaggerDocument.ApiProperty({ type: 'string' }), limit: SwaggerDocument.ApiProperty({ type: 'string' }), status: SwaggerDocument.ApiProperty({ type: 'string' }), @@ -9,7 +9,7 @@ ApiDocument.addModel('GetReportsDto', { endDate: SwaggerDocument.ApiProperty({ type: 'string' }), }); -export const GetReportsDto = body => ({ +export const ListReportsDto = body => ({ page: body.page, limit: body.limit, status: body.status, diff --git a/backend/src/core/modules/reports/interceptor/getReportsHistory.interceptor.js b/backend/src/core/modules/reports/interceptor/getReportsHistory.interceptor.js new file mode 100644 index 0000000..7cb5a6c --- /dev/null +++ b/backend/src/core/modules/reports/interceptor/getReportsHistory.interceptor.js @@ -0,0 +1,10 @@ +import { DefaultValidatorInterceptor } from 'core/infrastructure/interceptor'; +import { JoiUtils } from 'core/utils'; +import Joi from 'joi'; + +export const GetReportsHistoryInterceptor = new DefaultValidatorInterceptor( + Joi.object({ + page: JoiUtils.positiveNumber().min(1).default(1).required(), + limit: JoiUtils.positiveNumber().min(1).max(100).default(20).required(), + }) +); \ No newline at end of file diff --git a/backend/src/core/modules/reports/interceptor/index.js b/backend/src/core/modules/reports/interceptor/index.js index 330b988..c7d19ce 100644 --- a/backend/src/core/modules/reports/interceptor/index.js +++ b/backend/src/core/modules/reports/interceptor/index.js @@ -1,2 +1,3 @@ -export * from './getReports.interceptor'; -export * from './getReportsStats.interceptor'; \ No newline at end of file +export * from './listReports.interceptor'; +export * from './getReportsStats.interceptor'; +export * from './getReportsHistory.interceptor'; \ No newline at end of file diff --git a/backend/src/core/modules/reports/interceptor/getReports.interceptor.js b/backend/src/core/modules/reports/interceptor/listReports.interceptor.js similarity index 85% rename from backend/src/core/modules/reports/interceptor/getReports.interceptor.js rename to backend/src/core/modules/reports/interceptor/listReports.interceptor.js index 4060bd9..dda2dca 100644 --- a/backend/src/core/modules/reports/interceptor/getReports.interceptor.js +++ b/backend/src/core/modules/reports/interceptor/listReports.interceptor.js @@ -2,7 +2,7 @@ import { DefaultValidatorInterceptor } from 'core/infrastructure/interceptor'; import { JoiUtils } from 'core/utils'; import Joi from 'joi'; -export const GetReportsInterceptor = new DefaultValidatorInterceptor( +export const ListReportsInterceptor = new DefaultValidatorInterceptor( Joi.object({ page: JoiUtils.positiveNumber().min(1).default(1).required(), limit: JoiUtils.positiveNumber().min(1).max(100).default(20).required(), diff --git a/backend/src/core/modules/reports/reports.repository.js b/backend/src/core/modules/reports/reports.repository.js index b16d2ce..7a9c132 100644 --- a/backend/src/core/modules/reports/reports.repository.js +++ b/backend/src/core/modules/reports/reports.repository.js @@ -35,6 +35,25 @@ class Repository { } }); } + + async getReportsHistory(page, limit) { + return connection.reports.findMany({ + where: { + deleted_at: null, + }, + orderBy: { + created_at: 'asc', + }, + select: { + target_user_id: true, + reporter_id: true, + reason: true, + status: true, + }, + skip: (page - 1) * limit, + take: limit, + }); + } } export const ReportsRepository = new Repository(); \ No newline at end of file diff --git a/backend/src/core/modules/reports/service/reports.service.js b/backend/src/core/modules/reports/service/reports.service.js index 8926d6b..751b048 100644 --- a/backend/src/core/modules/reports/service/reports.service.js +++ b/backend/src/core/modules/reports/service/reports.service.js @@ -12,35 +12,35 @@ class Service { this.repository = ReportsRepository; } - async getReports(getReportsDto) { + async listReports(listReportsDto) { let allStatus = ["pending", "processing", "resolved"]; - if (allStatus.indexOf(getReportsDto.status) == -1) { + if (allStatus.indexOf(listReportsDto.status) == -1) { throw new BadRequestException("Invalid Status value"); } - let startDate = getReportsDto.startDate - ? new Date(getReportsDto.startDate).toISOString() + let startDate = listReportsDto.startDate + ? new Date(listReportsDto.startDate).toISOString() : null; - let endDate = getReportsDto.endDate - ? new Date(getReportsDto.endDate).toISOString() + let endDate = listReportsDto.endDate + ? new Date(listReportsDto.endDate).toISOString() : null; if (startDate > endDate) { throw new BadRequestException("startDate must be before endDate"); } - let reports = await this.repository.getReportsByDateAndStatus(getReportsDto.status, startDate, endDate); - reports = reports.slice((getReportsDto.page - 1) * getReportsDto.limit, getReportsDto.page * getReportsDto.limit); + let reports = await this.repository.getReportsByDateAndStatus(listReportsDto.status, startDate, endDate); + reports = reports.slice((listReportsDto.page - 1) * listReportsDto.limit, listReportsDto.page * listReportsDto.limit); return { data: { items: { reports: reports, }, pagination: { - page: getReportsDto.page, - limit: getReportsDto.limit, + page: listReportsDto.page, + limit: listReportsDto.limit, total: reports.length, - totalPages: reports.length / getReportsDto.limit, + totalPages: Math.ceil(reports.length / listReportsDto.limit), } } }; @@ -70,6 +70,23 @@ class Service { } }; } + + async getReportsHistory(getReportsHistoryDto) { + let reports = await this.repository.getReportsHistory(parseInt(getReportsHistoryDto.page), parseInt(getReportsHistoryDto.limit)); + return { + data: { + items: { + reports: reports, + }, + pagination: { + page: parseInt(getReportsHistoryDto.page), + limit: parseInt(getReportsHistoryDto.limit), + total: reports.length, + totalPages: Math.ceil(reports.length / parseInt(getReportsHistoryDto.limit)), + } + } + }; + } } export const ReportsService = new Service(); diff --git a/backend/yarn.lock b/backend/yarn.lock index 7f73cda..c2f8bc7 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -2819,16 +2819,11 @@ dot-prop@^5.1.0: dependencies: is-obj "^2.0.0" -dotenv@^17.3.1: +dotenv@^17.3.1, dotenv@^17.4.2: version "17.4.2" resolved "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz" integrity sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw== -dotenv@^8.2.0: - version "8.6.0" - resolved "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz" - integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g== - duck@^0.1.12: version "0.1.12" resolved "https://registry.npmjs.org/duck/-/duck-0.1.12.tgz" @@ -3550,11 +3545,6 @@ fs.realpath@^1.0.0: resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@~2.3.2: - version "2.3.3" - resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - ftp@^0.3.10: version "0.3.10" resolved "https://registry.npmjs.org/ftp/-/ftp-0.3.10.tgz" From 3c612931c4603503e01fc4b71e50ad9d0e61b57c Mon Sep 17 00:00:00 2001 From: DQGiac Date: Sun, 5 Jul 2026 22:14:34 +0700 Subject: [PATCH 26/26] feat/FI-112: implement get report by id --- .../core/api/reports/reports.controller.js | 4 ++++ .../src/core/api/reports/reports.resolver.js | 13 +++++++++++- .../src/core/common/swagger/reportFilter.js | 4 ++-- .../modules/reports/reports.repository.js | 8 +++++++- .../reports/service/reports.service.js | 20 ++++++++++++++++++- 5 files changed, 44 insertions(+), 5 deletions(-) diff --git a/backend/src/core/api/reports/reports.controller.js b/backend/src/core/api/reports/reports.controller.js index 4c84b68..39bd703 100644 --- a/backend/src/core/api/reports/reports.controller.js +++ b/backend/src/core/api/reports/reports.controller.js @@ -15,6 +15,10 @@ class Controller { const data = await this.service.getReportsStats(req.query); return ValidHttpResponse.toOkResponse(data); } + getReportById = async req => { + const data = await this.service.getReportById(req.params.id); + return ValidHttpResponse.toOkResponse(data); + } getReportsHistory = async req => { const data = await this.service.getReportsHistory(req.query); return ValidHttpResponse.toOkResponse(data); diff --git a/backend/src/core/api/reports/reports.resolver.js b/backend/src/core/api/reports/reports.resolver.js index da246ed..32f4192 100644 --- a/backend/src/core/api/reports/reports.resolver.js +++ b/backend/src/core/api/reports/reports.resolver.js @@ -8,6 +8,8 @@ import { uploadMediaSwagger } from 'core/common/swagger'; import { MediaInterceptor } from 'core/modules/document'; import { hasAdminRole } from 'core/modules/auth/guard'; import { ListReportsParams, GetReportsStatsParams, GetReportsHistoryParams } from 'core/common/swagger/reportFilter'; +import { RecordIdInterceptor } from 'core/modules/interceptor/recordId/record-id.interceptor'; +import { RecordId } from 'core/common/swagger/record-id'; export const ReportsResolver = Module.builder() .addPrefix({ @@ -17,13 +19,22 @@ export const ReportsResolver = Module.builder() }) .register([ { - route: '/', + route: '', method: 'get', params: ListReportsParams, guards: [hasAdminRole], controller: ReportsController.listReports, preAuthorization: true, }, + { + route: '/:id', + method: 'get', + guards: [hasAdminRole], + params: [RecordId], + interceptor: [RecordIdInterceptor], + controller: ReportsController.getReportById, + preAuthorization: true, + }, { route: '/stats', method: 'get', diff --git a/backend/src/core/common/swagger/reportFilter.js b/backend/src/core/common/swagger/reportFilter.js index e1efca1..643f2f8 100644 --- a/backend/src/core/common/swagger/reportFilter.js +++ b/backend/src/core/common/swagger/reportFilter.js @@ -15,11 +15,11 @@ export const ListReportsCriteriaDocument = { type: 'int', description: desc }), - status: (desc = 'Of three types: pending, processing, and resolved') => SwaggerDocument.ApiParams({ + status: (desc = 'Of two types: pending and resolved') => SwaggerDocument.ApiParams({ name: 'status', paramsIn: 'query', required: true, - type: 'date', + type: 'string', description: desc }), startDate: desc => SwaggerDocument.ApiParams({ diff --git a/backend/src/core/modules/reports/reports.repository.js b/backend/src/core/modules/reports/reports.repository.js index 7a9c132..26e1ba5 100644 --- a/backend/src/core/modules/reports/reports.repository.js +++ b/backend/src/core/modules/reports/reports.repository.js @@ -26,7 +26,7 @@ class Repository { gte: startDate, lte: endDate }, - deleted_at: NullTypes, + deleted_at: null, }, select: { target_user_id: true, @@ -36,6 +36,12 @@ class Repository { }); } + async getReportById(id) { + return connection.reports.findFirst({ + where: { target_user_id: id, deleted_at: null } + }); + } + async getReportsHistory(page, limit) { return connection.reports.findMany({ where: { diff --git a/backend/src/core/modules/reports/service/reports.service.js b/backend/src/core/modules/reports/service/reports.service.js index 751b048..349f3ba 100644 --- a/backend/src/core/modules/reports/service/reports.service.js +++ b/backend/src/core/modules/reports/service/reports.service.js @@ -13,7 +13,7 @@ class Service { } async listReports(listReportsDto) { - let allStatus = ["pending", "processing", "resolved"]; + let allStatus = ["pending", "resolved"]; if (allStatus.indexOf(listReportsDto.status) == -1) { throw new BadRequestException("Invalid Status value"); } @@ -71,6 +71,24 @@ class Service { }; } + async getReportById(id) { + let report = await this.repository.getReportById(id); + if (!report) { + throw new BadRequestException("Report not found"); + } + + return { + data: { + targetUserId: report.target_user_id, + reporterId: report.reporter_id, + reason: report.reason, + status: report.status, + createdAt: report.created_at, + updatedAt: report.updated_at, + } + } + } + async getReportsHistory(getReportsHistoryDto) { let reports = await this.repository.getReportsHistory(parseInt(getReportsHistoryDto.page), parseInt(getReportsHistoryDto.limit)); return {