From 90321bcb16a7ab3d742a46d24af46b1df705e2d7 Mon Sep 17 00:00:00 2001 From: Gustavo Perdomo Date: Thu, 30 Oct 2025 22:34:07 -0300 Subject: [PATCH 01/10] feat: prisma improvements --- .gitignore | 2 + jest.config.js | 3 +- package-lock.json | 1012 ++++++++--------- package.json | 8 +- prisma.config.ts | 12 + prisma/{schema.prisma => schema/auth.prisma} | 46 +- prisma/schema/file.prisma | 18 + prisma/schema/schema.prisma | 11 + prisma/seed.ts | 4 +- .../database/prisma/prisma.module.ts | 2 +- .../database/prisma/prisma.service.ts | 9 +- .../email-verification.repository.ts | 8 +- .../repositories/otp.repository.ts | 6 +- .../repositories/password-reset.repository.ts | 8 +- .../repositories/permission.repository.ts | 6 +- .../repositories/refresh-token.repository.ts | 8 +- .../repositories/role.repository.ts | 12 +- .../repositories/user.repository.ts | 18 +- tsconfig.json | 3 +- 19 files changed, 607 insertions(+), 589 deletions(-) create mode 100644 prisma.config.ts rename prisma/{schema.prisma => schema/auth.prisma} (77%) create mode 100644 prisma/schema/file.prisma create mode 100644 prisma/schema/schema.prisma diff --git a/.gitignore b/.gitignore index dd00dcc8..67780cc6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # compiled output /dist /node_modules +/build # Logs logs @@ -49,3 +50,4 @@ lerna-debug.log* # Prisma /prisma/.env /prisma/migrations/dev +/generated/prisma \ No newline at end of file diff --git a/jest.config.js b/jest.config.js index 12047f86..adf0774a 100644 --- a/jest.config.js +++ b/jest.config.js @@ -28,8 +28,9 @@ module.exports = { }, }, moduleNameMapper: { - '^@core/(.*)$': '/src/core/$1', '^@application/(.*)$': '/src/application/$1', + '^@core/(.*)$': '/src/core/$1', + '^@generated/(.*)$': '/generated/$1', '^@infrastructure/(.*)$': '/src/infrastructure/$1', '^@presentation/(.*)$': '/src/presentation/$1', '^@shared/(.*)$': '/src/shared/$1', diff --git a/package-lock.json b/package-lock.json index a19cf26b..731d1f59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,13 @@ { "name": "nestjs-template", - "version": "0.1.0", + "version": "2.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "nestjs-template", - "version": "0.1.0", + "version": "2.0.1", + "hasInstallScript": true, "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.787.0", @@ -20,7 +21,8 @@ "@nestjs/passport": "^11.0.5", "@nestjs/platform-express": "^11.0.15", "@nestjs/swagger": "^11.1.0", - "@prisma/client": "^6.5.0", + "@prisma/adapter-pg": "6.18.0", + "@prisma/client": "6.18.0", "@types/multer": "^1.4.12", "bcrypt": "^5.1.1", "class-transformer": "^0.5.1", @@ -35,7 +37,6 @@ "nodemailer": "^6.10.0", "passport": "^0.7.0", "passport-jwt": "^4.0.1", - "prisma": "^6.5.0", "qrcode": "^1.5.4", "reflect-metadata": "^0.2.2", "speakeasy": "^2.0.0", @@ -65,6 +66,7 @@ "jest": "^29.7.0", "lint-staged": "^16.0.0", "prettier": "^3.5.3", + "prisma": "6.18.0", "source-map-support": "^0.5.21", "supertest": "^7.1.0", "ts-jest": "^29.3.1", @@ -410,6 +412,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.787.0.tgz", "integrity": "sha512-eGLCWkN0NlntJ9yPU6OKUggVS4cFvuZJog+cFg1KD5hniLqz7Y0YRtB4uBxW212fK3XCfddgyscEOEeHaTQQTw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", @@ -1149,6 +1152,7 @@ "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", @@ -1669,406 +1673,6 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", - "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz", - "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz", - "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz", - "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz", - "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz", - "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz", - "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz", - "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz", - "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz", - "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz", - "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz", - "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz", - "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz", - "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz", - "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz", - "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz", - "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz", - "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz", - "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz", - "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz", - "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz", - "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz", - "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz", - "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz", - "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz", @@ -3402,6 +3006,7 @@ "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.1.tgz", "integrity": "sha512-crzp+1qeZ5EGL0nFTPy9NrVMAaUWewV5AwtQyv6SQ9yQPXwRl9W9hm1pt0nAtUu5QbYMbSuo7lYcF81EjM+nCA==", "license": "MIT", + "peer": true, "dependencies": { "file-type": "20.5.0", "iterare": "1.2.1", @@ -3449,6 +3054,7 @@ "integrity": "sha512-micQrbh9iL0PuYVx2vsUojuNmMUyqoMCuj7eGAUhvjiZUh4DBLPdxYmJEayCT/equHSiw9vNC95Vm0JigVZ44g==", "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "@nuxt/opencollective": "0.4.1", "fast-safe-stringify": "2.1.1", @@ -3544,6 +3150,7 @@ "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.0.15.tgz", "integrity": "sha512-+GAaqVkUkfpwn6iMuysPat8DwY7jmcE2l9/1n9f8aSw7sZ+VEZ4h1aCAFlWBDhivpsKbq6D7wCnw0ksJFHf5vQ==", "license": "MIT", + "peer": true, "dependencies": { "cors": "2.8.5", "express": "5.1.0", @@ -3785,10 +3392,21 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/@prisma/adapter-pg": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/adapter-pg/-/adapter-pg-6.18.0.tgz", + "integrity": "sha512-eBtBOL1z2Sh0Rc2hwa4dmwoNeFs5T69tsA62AkhBvnzNfTcr/YfLeJae/wjNmvRttYDPmdiuhqZCRPNY1+8fOA==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/driver-adapter-utils": "6.18.0", + "pg": "^8.11.3", + "postgres-array": "3.0.4" + } + }, "node_modules/@prisma/client": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.5.0.tgz", - "integrity": "sha512-M6w1Ql/BeiGoZmhMdAZUXHu5sz5HubyVcKukbLs3l0ELcQb8hTUJxtGEChhv4SVJ0QJlwtLnwOLgIRQhpsm9dw==", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.18.0.tgz", + "integrity": "sha512-jnL2I9gDnPnw4A+4h5SuNn8Gc+1mL1Z79U/3I9eE2gbxJG1oSA+62ByPW4xkeDgwE0fqMzzpAZ7IHxYnLZ4iQA==", "hasInstallScript": true, "license": "Apache-2.0", "engines": { @@ -3808,58 +3426,74 @@ } }, "node_modules/@prisma/config": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.5.0.tgz", - "integrity": "sha512-sOH/2Go9Zer67DNFLZk6pYOHj+rumSb0VILgltkoxOjYnlLqUpHPAN826vnx8HigqnOCxj9LRhT6U7uLiIIWgw==", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.18.0.tgz", + "integrity": "sha512-rgFzspCpwsE+q3OF/xkp0fI2SJ3PfNe9LLMmuSVbAZ4nN66WfBiKqJKo/hLz3ysxiPQZf8h1SMf2ilqPMeWATQ==", + "devOptional": true, "license": "Apache-2.0", "dependencies": { - "esbuild": ">=0.12 <1", - "esbuild-register": "3.6.0" + "c12": "3.1.0", + "deepmerge-ts": "7.1.5", + "effect": "3.18.4", + "empathic": "2.0.0" } }, "node_modules/@prisma/debug": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.5.0.tgz", - "integrity": "sha512-fc/nusYBlJMzDmDepdUtH9aBsJrda2JNErP9AzuHbgUEQY0/9zQYZdNlXmKoIWENtio+qarPNe/+DQtrX5kMcQ==", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.18.0.tgz", + "integrity": "sha512-PMVPMmxPj0ps1VY75DIrT430MoOyQx9hmm174k6cmLZpcI95rAPXOQ+pp8ANQkJtNyLVDxnxVJ0QLbrm/ViBcg==", "license": "Apache-2.0" }, + "node_modules/@prisma/driver-adapter-utils": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-6.18.0.tgz", + "integrity": "sha512-9wgSriEKs4j1ePxlv1/RNfJV9Gu5rzG37Neshg+DfrCcUY3amroERvTjyR04w5J1THdGdOTgGL9VdJcVaKRMmQ==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.18.0" + } + }, "node_modules/@prisma/engines": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.5.0.tgz", - "integrity": "sha512-FVPQYHgOllJklN9DUyujXvh3hFJCY0NX86sDmBErLvoZjy2OXGiZ5FNf3J/C4/RZZmCypZBYpBKEhx7b7rEsdw==", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.18.0.tgz", + "integrity": "sha512-i5RzjGF/ex6AFgqEe2o1IW8iIxJGYVQJVRau13kHPYEL1Ck8Zvwuzamqed/1iIljs5C7L+Opiz5TzSsUebkriA==", + "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.5.0", - "@prisma/engines-version": "6.5.0-73.173f8d54f8d52e692c7e27e72a88314ec7aeff60", - "@prisma/fetch-engine": "6.5.0", - "@prisma/get-platform": "6.5.0" + "@prisma/debug": "6.18.0", + "@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", + "@prisma/fetch-engine": "6.18.0", + "@prisma/get-platform": "6.18.0" } }, "node_modules/@prisma/engines-version": { - "version": "6.5.0-73.173f8d54f8d52e692c7e27e72a88314ec7aeff60", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.5.0-73.173f8d54f8d52e692c7e27e72a88314ec7aeff60.tgz", - "integrity": "sha512-iK3EmiVGFDCmXjSpdsKGNqy9hOdLnvYBrJB61far/oP03hlIxrb04OWmDjNTwtmZ3UZdA5MCvI+f+3k2jPTflQ==", + "version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f.tgz", + "integrity": "sha512-T7Af4QsJQnSgWN1zBbX+Cha5t4qjHRxoeoWpK4JugJzG/ipmmDMY5S+O0N1ET6sCBNVkf6lz+Y+ZNO9+wFU8pQ==", + "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/fetch-engine": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.5.0.tgz", - "integrity": "sha512-3LhYA+FXP6pqY8FLHCjewyE8pGXXJ7BxZw2rhPq+CZAhvflVzq4K8Qly3OrmOkn6wGlz79nyLQdknyCG2HBTuA==", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.18.0.tgz", + "integrity": "sha512-TdaBvTtBwP3IoqVYoGIYpD4mWlk0pJpjTJjir/xLeNWlwog7Sl3bD2J0jJ8+5+q/6RBg+acb9drsv5W6lqae7A==", + "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.5.0", - "@prisma/engines-version": "6.5.0-73.173f8d54f8d52e692c7e27e72a88314ec7aeff60", - "@prisma/get-platform": "6.5.0" + "@prisma/debug": "6.18.0", + "@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", + "@prisma/get-platform": "6.18.0" } }, "node_modules/@prisma/get-platform": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.5.0.tgz", - "integrity": "sha512-xYcvyJwNMg2eDptBYFqFLUCfgi+wZLcj6HDMsj0Qw0irvauG4IKmkbywnqwok0B+k+W+p+jThM2DKTSmoPCkzw==", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.18.0.tgz", + "integrity": "sha512-uXNJCJGhxTCXo2B25Ta91Rk1/Nmlqg9p7G9GKh8TPhxvAyXCvMNQoogj4JLEUy+3ku8g59cpyQIKFhqY2xO2bg==", + "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.5.0" + "@prisma/debug": "6.18.0" } }, "node_modules/@scarf/scarf": { @@ -4626,6 +4260,13 @@ "node": ">=18.0.0" } }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "devOptional": true, + "license": "MIT" + }, "node_modules/@tokenizer/inflate": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz", @@ -4758,6 +4399,7 @@ "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -4904,6 +4546,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.17.tgz", "integrity": "sha512-nAJuQXoyPj04uLgu+obZcSmsfOenUg6DxPKogeUy6yNCFwWaj5sBF8/G/pNo8EtBJjAfSVgfIlugR/BCOleO+g==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.20.0" } @@ -5076,6 +4719,7 @@ "integrity": "sha512-8C0+jlNJOwQso2GapCVWWfW/rzaq7Lbme+vGUFKE31djwNncIpgXD7Cd4weEsDdkoZDjH0lwwr3QDQFuyrMg9g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.29.0", "@typescript-eslint/types": "8.29.0", @@ -5446,6 +5090,7 @@ "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5494,6 +5139,7 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -6046,6 +5692,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -6135,17 +5782,59 @@ "dependencies": { "streamsearch": "^1.1.0" }, - "engines": { - "node": ">=10.16.0" + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/c12": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz", + "integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.3", + "confbox": "^0.2.2", + "defu": "^6.1.4", + "dotenv": "^16.6.1", + "exsolve": "^1.0.7", + "giget": "^2.0.0", + "jiti": "^2.4.2", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^1.0.0", + "pkg-types": "^2.2.0", + "rc9": "^2.1.2" + }, + "peerDependencies": { + "magicast": "^0.3.5" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } } }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", + "node_modules/c12/node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "devOptional": true, + "license": "BSD-2-Clause", "engines": { - "node": ">= 0.8" + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" } }, "node_modules/call-bind": { @@ -6273,8 +5962,9 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, + "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "readdirp": "^4.0.1" }, @@ -6320,6 +6010,16 @@ "node": ">=8" } }, + "node_modules/citty": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "consola": "^3.2.3" + } + }, "node_modules/cjs-module-lexer": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", @@ -6331,13 +6031,15 @@ "version": "0.5.1", "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/class-validator": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.1.tgz", "integrity": "sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==", "license": "MIT", + "peer": true, "dependencies": { "@types/validator": "^13.11.8", "libphonenumber-js": "^1.10.53", @@ -6627,6 +6329,13 @@ "typedarray": "^0.0.6" } }, + "node_modules/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "devOptional": true, + "license": "MIT" + }, "node_modules/consola": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", @@ -6852,6 +6561,16 @@ "node": ">=0.10.0" } }, + "node_modules/deepmerge-ts": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", + "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", + "devOptional": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/defaults": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", @@ -6882,6 +6601,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "devOptional": true, + "license": "MIT" + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -6907,6 +6633,13 @@ "node": ">= 0.8" } }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "devOptional": true, + "license": "MIT" + }, "node_modules/detect-libc": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", @@ -7026,6 +6759,17 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, + "node_modules/effect": { + "version": "3.18.4", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.18.4.tgz", + "integrity": "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "fast-check": "^3.23.1" + } + }, "node_modules/ejs": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", @@ -7068,6 +6812,16 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, + "node_modules/empathic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", + "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -7167,58 +6921,6 @@ "node": ">= 0.4" } }, - "node_modules/esbuild": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", - "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.2", - "@esbuild/android-arm": "0.25.2", - "@esbuild/android-arm64": "0.25.2", - "@esbuild/android-x64": "0.25.2", - "@esbuild/darwin-arm64": "0.25.2", - "@esbuild/darwin-x64": "0.25.2", - "@esbuild/freebsd-arm64": "0.25.2", - "@esbuild/freebsd-x64": "0.25.2", - "@esbuild/linux-arm": "0.25.2", - "@esbuild/linux-arm64": "0.25.2", - "@esbuild/linux-ia32": "0.25.2", - "@esbuild/linux-loong64": "0.25.2", - "@esbuild/linux-mips64el": "0.25.2", - "@esbuild/linux-ppc64": "0.25.2", - "@esbuild/linux-riscv64": "0.25.2", - "@esbuild/linux-s390x": "0.25.2", - "@esbuild/linux-x64": "0.25.2", - "@esbuild/netbsd-arm64": "0.25.2", - "@esbuild/netbsd-x64": "0.25.2", - "@esbuild/openbsd-arm64": "0.25.2", - "@esbuild/openbsd-x64": "0.25.2", - "@esbuild/sunos-x64": "0.25.2", - "@esbuild/win32-arm64": "0.25.2", - "@esbuild/win32-ia32": "0.25.2", - "@esbuild/win32-x64": "0.25.2" - } - }, - "node_modules/esbuild-register": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz", - "integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==", - "license": "MIT", - "dependencies": { - "debug": "^4.3.4" - }, - "peerDependencies": { - "esbuild": ">=0.12 <1" - } - }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -7254,6 +6956,7 @@ "integrity": "sha512-jV7AbNoFPAY1EkFYpLq5bslU9NLNO8xnEeQXwErNibVryjk67wHVmddTBilc5srIttJDBrB0eMHKZBFbSIABCw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -7315,6 +7018,7 @@ "integrity": "sha512-4EQQr6wXwS+ZJSzaR5ZCrYgLxqvUjdXctaEtBqHcbkW944B1NQyO4qpdHQbXBONfwxXdkAY81HH4+LUfrg+zPw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -7621,6 +7325,7 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", @@ -7707,6 +7412,13 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/exsolve": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", + "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", + "devOptional": true, + "license": "MIT" + }, "node_modules/external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -7735,6 +7447,29 @@ "node": ">=0.10.0" } }, + "node_modules/fast-check": { + "version": "3.23.2", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", + "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", + "devOptional": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "dependencies": { + "pure-rand": "^6.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -8439,6 +8174,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/giget": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", + "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.0", + "defu": "^6.1.4", + "node-fetch-native": "^1.6.6", + "nypm": "^0.6.0", + "pathe": "^2.0.3" + }, + "bin": { + "giget": "dist/cli.mjs" + } + }, "node_modules/glob": { "version": "11.0.1", "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.1.tgz", @@ -9190,6 +8943,7 @@ "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -9893,6 +9647,17 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "devOptional": true, + "license": "MIT", + "peer": true, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -11109,6 +10874,13 @@ } } }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "devOptional": true, + "license": "MIT" + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -11182,6 +10954,26 @@ "set-blocking": "^2.0.0" } }, + "node_modules/nypm": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz", + "integrity": "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.2", + "pathe": "^2.0.3", + "pkg-types": "^2.3.0", + "tinyexec": "^1.0.1" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, + "engines": { + "node": "^14.16.0 || >=16.10.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -11203,6 +10995,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "devOptional": true, + "license": "MIT" + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -11418,6 +11217,7 @@ "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", "license": "MIT", + "peer": true, "dependencies": { "passport-strategy": "1.x.x", "pause": "0.0.1", @@ -11530,6 +11330,13 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "devOptional": true, + "license": "MIT" + }, "node_modules/pause": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", @@ -11548,6 +11355,112 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/pg": { + "version": "8.16.3", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", + "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", + "license": "MIT", + "peer": true, + "dependencies": { + "pg-connection-string": "^2.9.1", + "pg-pool": "^3.10.1", + "pg-protocol": "^1.10.3", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.2.7" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz", + "integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", + "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz", + "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz", + "integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pg-types/node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -11660,6 +11573,18 @@ "node": ">=8" } }, + "node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, "node_modules/pluralize": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", @@ -11688,6 +11613,45 @@ "node": ">= 0.4" } }, + "node_modules/postgres-array": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.4.tgz", + "integrity": "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -11704,6 +11668,7 @@ "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -11756,14 +11721,16 @@ } }, "node_modules/prisma": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.5.0.tgz", - "integrity": "sha512-yUGXmWqv5F4PByMSNbYFxke/WbnyTLjnJ5bKr8fLkcnY7U5rU9rUTh/+Fja+gOrRxEgtCbCtca94IeITj4j/pg==", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.18.0.tgz", + "integrity": "sha512-bXWy3vTk8mnRmT+SLyZBQoC2vtV9Z8u7OHvEu+aULYxwiop/CPiFZ+F56KsNRNf35jw+8wcu8pmLsjxpBxAO9g==", + "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { - "@prisma/config": "6.5.0", - "@prisma/engines": "6.5.0" + "@prisma/config": "6.18.0", + "@prisma/engines": "6.18.0" }, "bin": { "prisma": "build/index.js" @@ -11771,9 +11738,6 @@ "engines": { "node": ">=18.18" }, - "optionalDependencies": { - "fsevents": "2.3.3" - }, "peerDependencies": { "typescript": ">=5.1.0" }, @@ -11830,7 +11794,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", - "dev": true, + "devOptional": true, "funding": [ { "type": "individual", @@ -12082,6 +12046,17 @@ "node": ">= 0.8" } }, + "node_modules/rc9": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", + "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.4", + "destr": "^2.0.3" + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -12114,7 +12089,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 14.18.0" @@ -12128,7 +12103,8 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/repeat-string": { "version": "1.6.1", @@ -12451,6 +12427,7 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -12787,6 +12764,15 @@ "node": ">=6" } }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -13506,6 +13492,13 @@ "node": ">= 6" } }, + "node_modules/tinyexec": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", + "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==", + "devOptional": true, + "license": "MIT" + }, "node_modules/tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -13683,6 +13676,7 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -13830,6 +13824,7 @@ "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -14079,6 +14074,7 @@ "integrity": "sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.6", diff --git a/package.json b/package.json index ae1ca8a1..aee1b001 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,8 @@ "db:migrate": "prisma migrate dev --name init", "db:push": "prisma db push", "db:seed": "ts-node -r tsconfig-paths/register prisma/seed.ts", - "prepare": "husky" + "prepare": "husky", + "postinstall": "npm run db:generate" }, "dependencies": { "@aws-sdk/client-s3": "^3.787.0", @@ -55,7 +56,8 @@ "@nestjs/passport": "^11.0.5", "@nestjs/platform-express": "^11.0.15", "@nestjs/swagger": "^11.1.0", - "@prisma/client": "^6.5.0", + "@prisma/adapter-pg": "6.18.0", + "@prisma/client": "6.18.0", "@types/multer": "^1.4.12", "bcrypt": "^5.1.1", "class-transformer": "^0.5.1", @@ -70,7 +72,6 @@ "nodemailer": "^6.10.0", "passport": "^0.7.0", "passport-jwt": "^4.0.1", - "prisma": "^6.5.0", "qrcode": "^1.5.4", "reflect-metadata": "^0.2.2", "speakeasy": "^2.0.0", @@ -100,6 +101,7 @@ "jest": "^29.7.0", "lint-staged": "^16.0.0", "prettier": "^3.5.3", + "prisma": "6.18.0", "source-map-support": "^0.5.21", "supertest": "^7.1.0", "ts-jest": "^29.3.1", diff --git a/prisma.config.ts b/prisma.config.ts new file mode 100644 index 00000000..51d7419c --- /dev/null +++ b/prisma.config.ts @@ -0,0 +1,12 @@ +import { defineConfig, env } from 'prisma/config'; + +export default defineConfig({ + schema: 'prisma/schema', + migrations: { + path: 'prisma/migrations', + }, + engine: 'classic', + datasource: { + url: env('DATABASE_URL'), + }, +}); diff --git a/prisma/schema.prisma b/prisma/schema/auth.prisma similarity index 77% rename from prisma/schema.prisma rename to prisma/schema/auth.prisma index 24512b37..0a3c4426 100644 --- a/prisma/schema.prisma +++ b/prisma/schema/auth.prisma @@ -1,15 +1,3 @@ -// This is your Prisma schema file, -// learn more about it in the docs: https://pris.ly/d/prisma-schema - -generator client { - provider = "prisma-client-js" - seed = "ts-node -r tsconfig-paths/register prisma/seed.ts" -} - -datasource db { - provider = "postgresql" - url = env("DATABASE_URL") -} model User { id String @id @default(uuid()) @@ -23,7 +11,7 @@ model User { lastLoginAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - + roles UserRole[] otps Otp[] refreshTokens RefreshToken[] @@ -38,7 +26,7 @@ model Role { isDefault Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - + users UserRole[] permissions RolePermission[] } @@ -51,7 +39,7 @@ model Permission { action String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - + roles RolePermission[] } @@ -97,44 +85,26 @@ model RefreshToken { model EmailVerification { id String @id @default(uuid()) - email String + email String code String expiresAt DateTime verifiedAt DateTime? createdAt DateTime @default(now()) - + @@index([email]) } model PasswordReset { id String @id @default(uuid()) userId String - email String + email String token String @unique expiresAt DateTime usedAt DateTime? createdAt DateTime @default(now()) - + user User @relation(fields: [userId], references: [id], onDelete: Cascade) - + @@index([email]) @@index([token]) -} - -model File { - id String @id @default(uuid()) - filename String - originalName String - path String - mimeType String - size Int - bucket String - userId String? - isPublic Boolean @default(false) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - user User? @relation(fields: [userId], references: [id], onDelete: SetNull) - - @@index([userId]) } \ No newline at end of file diff --git a/prisma/schema/file.prisma b/prisma/schema/file.prisma new file mode 100644 index 00000000..c20ca5c8 --- /dev/null +++ b/prisma/schema/file.prisma @@ -0,0 +1,18 @@ + +model File { + id String @id @default(uuid()) + filename String + originalName String + path String + mimeType String + size Int + bucket String + userId String? + isPublic Boolean @default(false) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + user User? @relation(fields: [userId], references: [id], onDelete: SetNull) + + @@index([userId]) +} \ No newline at end of file diff --git a/prisma/schema/schema.prisma b/prisma/schema/schema.prisma new file mode 100644 index 00000000..b7f483da --- /dev/null +++ b/prisma/schema/schema.prisma @@ -0,0 +1,11 @@ +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +generator client { + provider = "prisma-client" + engineType = "client" + moduleFormat = "cjs" + output = "../../generated/prisma" +} \ No newline at end of file diff --git a/prisma/seed.ts b/prisma/seed.ts index aa53df36..00b00127 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -1,6 +1,6 @@ -import { PrismaClient } from '@prisma/client'; +import { PrismaClient } from '@generated/prisma/client'; import * as bcrypt from 'bcrypt'; -import { ResourceType, ActionType } from '../src/core/value-objects/resource-action.vo'; +import { ActionType, ResourceType } from '../src/core/value-objects/resource-action.vo'; // Roles const roles = [ diff --git a/src/infrastructure/database/prisma/prisma.module.ts b/src/infrastructure/database/prisma/prisma.module.ts index 23c626eb..7207426f 100644 --- a/src/infrastructure/database/prisma/prisma.module.ts +++ b/src/infrastructure/database/prisma/prisma.module.ts @@ -1,4 +1,4 @@ -import { Module, Global } from '@nestjs/common'; +import { Global, Module } from '@nestjs/common'; import { PrismaService } from './prisma.service'; @Global() diff --git a/src/infrastructure/database/prisma/prisma.service.ts b/src/infrastructure/database/prisma/prisma.service.ts index a956d3ae..eb61148e 100644 --- a/src/infrastructure/database/prisma/prisma.service.ts +++ b/src/infrastructure/database/prisma/prisma.service.ts @@ -1,10 +1,15 @@ -import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'; -import { PrismaClient } from '@prisma/client'; +import { PrismaClient } from '@generated/prisma/client'; +import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common'; +import { PrismaPg } from '@prisma/adapter-pg'; @Injectable() export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy { constructor() { + const connectionString = `${process.env.DATABASE_URL}`; + const adapter = new PrismaPg({ connectionString }); + super({ + adapter, log: process.env.NODE_ENV === 'development' ? ['query', 'info', 'warn', 'error'] : ['error'], }); } diff --git a/src/infrastructure/repositories/email-verification.repository.ts b/src/infrastructure/repositories/email-verification.repository.ts index 44f1721b..f9fe3a36 100644 --- a/src/infrastructure/repositories/email-verification.repository.ts +++ b/src/infrastructure/repositories/email-verification.repository.ts @@ -1,11 +1,11 @@ -import { Injectable } from '@nestjs/common'; import { EmailVerification } from '@core/entities/email-verification.entity'; import { IEmailVerificationRepository } from '@core/repositories/email-verification.repository.interface'; -import { PrismaService } from '@infrastructure/database/prisma/prisma.service'; -import { BaseRepository } from './base.repository'; import { Email } from '@core/value-objects/email.vo'; import { VerificationCode } from '@core/value-objects/verification-code.vo'; -import { EmailVerification as PrismaEmailVerification } from '@prisma/client'; +import { EmailVerification as PrismaEmailVerification } from '@generated/prisma/client'; +import { PrismaService } from '@infrastructure/database/prisma/prisma.service'; +import { Injectable } from '@nestjs/common'; +import { BaseRepository } from './base.repository'; @Injectable() export class EmailVerificationRepository diff --git a/src/infrastructure/repositories/otp.repository.ts b/src/infrastructure/repositories/otp.repository.ts index 58d26617..cfa4f2f8 100644 --- a/src/infrastructure/repositories/otp.repository.ts +++ b/src/infrastructure/repositories/otp.repository.ts @@ -1,11 +1,11 @@ -import { Injectable } from '@nestjs/common'; import { Otp } from '@core/entities/otp.entity'; import { IOtpRepository } from '@core/repositories/otp.repository.interface'; +import { UserId } from '@core/value-objects/user-id.vo'; +import { Otp as PrismaOtp } from '@generated/prisma/client'; import { PrismaService } from '@infrastructure/database/prisma/prisma.service'; +import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; -import { Otp as PrismaOtp } from '@prisma/client'; import { BaseRepository } from './base.repository'; -import { UserId } from '@core/value-objects/user-id.vo'; @Injectable() export class OtpRepository extends BaseRepository implements IOtpRepository { diff --git a/src/infrastructure/repositories/password-reset.repository.ts b/src/infrastructure/repositories/password-reset.repository.ts index 9b3fb09b..56592cba 100644 --- a/src/infrastructure/repositories/password-reset.repository.ts +++ b/src/infrastructure/repositories/password-reset.repository.ts @@ -1,12 +1,12 @@ -import { Injectable } from '@nestjs/common'; import { PasswordReset } from '@core/entities/password-reset.entity'; import { IPasswordResetRepository } from '@core/repositories/password-reset.repository.interface'; -import { PrismaService } from '@infrastructure/database/prisma/prisma.service'; -import { BaseRepository } from './base.repository'; import { Email } from '@core/value-objects/email.vo'; import { Token } from '@core/value-objects/token.vo'; import { UserId } from '@core/value-objects/user-id.vo'; -import { PasswordReset as PrismaPasswordReset } from '@prisma/client'; +import { PasswordReset as PrismaPasswordReset } from '@generated/prisma/client'; +import { PrismaService } from '@infrastructure/database/prisma/prisma.service'; +import { Injectable } from '@nestjs/common'; +import { BaseRepository } from './base.repository'; @Injectable() export class PasswordResetRepository diff --git a/src/infrastructure/repositories/permission.repository.ts b/src/infrastructure/repositories/permission.repository.ts index 0b72a074..6c7f0d70 100644 --- a/src/infrastructure/repositories/permission.repository.ts +++ b/src/infrastructure/repositories/permission.repository.ts @@ -1,9 +1,9 @@ -import { Injectable } from '@nestjs/common'; import { Permission } from '@core/entities/permission.entity'; import { IPermissionRepository } from '@core/repositories/permission.repository.interface'; +import { ActionType, ResourceAction } from '@core/value-objects/resource-action.vo'; +import { Permission as PrismaPermission } from '@generated/prisma/client'; import { PrismaService } from '@infrastructure/database/prisma/prisma.service'; -import { Permission as PrismaPermission } from '@prisma/client'; -import { ResourceAction, ActionType } from '@core/value-objects/resource-action.vo'; +import { Injectable } from '@nestjs/common'; import { BaseRepository } from './base.repository'; @Injectable() diff --git a/src/infrastructure/repositories/refresh-token.repository.ts b/src/infrastructure/repositories/refresh-token.repository.ts index 4c3bedc5..908cf5e8 100644 --- a/src/infrastructure/repositories/refresh-token.repository.ts +++ b/src/infrastructure/repositories/refresh-token.repository.ts @@ -1,12 +1,12 @@ -import { Injectable } from '@nestjs/common'; import { RefreshToken } from '@core/entities/refresh-token.entity'; import { IRefreshTokenRepository } from '@core/repositories/refresh-token.repository.interface'; +import { Token } from '@core/value-objects/token.vo'; +import { UserId } from '@core/value-objects/user-id.vo'; +import { RefreshToken as PrismaRefreshToken } from '@generated/prisma/client'; import { PrismaService } from '@infrastructure/database/prisma/prisma.service'; +import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; -import { RefreshToken as PrismaRefreshToken } from '@prisma/client'; import { BaseRepository } from './base.repository'; -import { UserId } from '@core/value-objects/user-id.vo'; -import { Token } from '@core/value-objects/token.vo'; @Injectable() export class RefreshTokenRepository diff --git a/src/infrastructure/repositories/role.repository.ts b/src/infrastructure/repositories/role.repository.ts index c067ceca..b72510c2 100644 --- a/src/infrastructure/repositories/role.repository.ts +++ b/src/infrastructure/repositories/role.repository.ts @@ -1,14 +1,14 @@ -import { Injectable } from '@nestjs/common'; -import { Role } from '@core/entities/role.entity'; import { Permission } from '@core/entities/permission.entity'; +import { Role } from '@core/entities/role.entity'; import { IRoleRepository } from '@core/repositories/role.repository.interface'; -import { PrismaService } from '@infrastructure/database/prisma/prisma.service'; +import { ActionType, ResourceAction } from '@core/value-objects/resource-action.vo'; import { + Permission as PrismaPermission, Role as PrismaRole, RolePermission as PrismaRolePermission, - Permission as PrismaPermission, -} from '@prisma/client'; -import { ResourceAction, ActionType } from '@core/value-objects/resource-action.vo'; +} from '@generated/prisma/client'; +import { PrismaService } from '@infrastructure/database/prisma/prisma.service'; +import { Injectable } from '@nestjs/common'; import { BaseRepository } from './base.repository'; // Define a type for Role with its related permissions diff --git a/src/infrastructure/repositories/user.repository.ts b/src/infrastructure/repositories/user.repository.ts index 0e41c0bd..bc7d9c57 100644 --- a/src/infrastructure/repositories/user.repository.ts +++ b/src/infrastructure/repositories/user.repository.ts @@ -1,17 +1,17 @@ -import { Injectable } from '@nestjs/common'; +import { Permission } from '@core/entities/permission.entity'; +import { Role } from '@core/entities/role.entity'; import { User } from '@core/entities/user.entity'; import { IUserRepository } from '@core/repositories/user.repository.interface'; -import { PrismaService } from '@infrastructure/database/prisma/prisma.service'; -import { Role } from '@core/entities/role.entity'; -import { Permission } from '@core/entities/permission.entity'; +import { ActionType, ResourceAction } from '@core/value-objects/resource-action.vo'; import { + Permission as PrismaPermission, + Role as PrismaRole, + RolePermission as PrismaRolePermission, User as PrismaUser, UserRole as PrismaUserRole, - RolePermission as PrismaRolePermission, - Role as PrismaRole, - Permission as PrismaPermission, -} from '@prisma/client'; -import { ResourceAction, ActionType } from '@core/value-objects/resource-action.vo'; +} from '@generated/prisma/client'; +import { PrismaService } from '@infrastructure/database/prisma/prisma.service'; +import { Injectable } from '@nestjs/common'; import { BaseRepository } from './base.repository'; // Define a type for User with its relations (roles with nested permissions) diff --git a/tsconfig.json b/tsconfig.json index 4544b4a3..2de6b563 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,8 +19,9 @@ "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": false, "paths": { - "@core/*": ["src/core/*"], "@application/*": ["src/application/*"], + "@core/*": ["src/core/*"], + "@generated/prisma/*": ["generated/prisma/*"], "@infrastructure/*": ["src/infrastructure/*"], "@presentation/*": ["src/presentation/*"], "@shared/*": ["src/shared/*"] From 4069836dadad526a29ae12f85621e19142f0109c Mon Sep 17 00:00:00 2001 From: Gustavo Perdomo Date: Fri, 31 Oct 2025 00:28:05 -0300 Subject: [PATCH 02/10] feat: prisma improvements --- prisma.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/prisma.config.ts b/prisma.config.ts index 51d7419c..9e2a3a3e 100644 --- a/prisma.config.ts +++ b/prisma.config.ts @@ -1,3 +1,4 @@ +import 'dotenv/config'; import { defineConfig, env } from 'prisma/config'; export default defineConfig({ From 517f4a961d2a215282b5c79961889a55eac792c3 Mon Sep 17 00:00:00 2001 From: Gustavo Perdomo Date: Fri, 31 Oct 2025 00:32:44 -0300 Subject: [PATCH 03/10] feat: prisma improvements --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f56dd09d..d06fdd7e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,6 +8,7 @@ on: env: NODE_VERSION: '18.x' + DATABASE_URL: '' jobs: lint: From aa3c4d6b0d58c8e2382ccdb3b093ad9fd7e9a849 Mon Sep 17 00:00:00 2001 From: Gustavo Perdomo Date: Fri, 31 Oct 2025 00:37:06 -0300 Subject: [PATCH 04/10] feat: prisma improvements --- .github/workflows/main.yml | 1 - package.json | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d06fdd7e..f56dd09d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,7 +8,6 @@ on: env: NODE_VERSION: '18.x' - DATABASE_URL: '' jobs: lint: diff --git a/package.json b/package.json index aee1b001..87b2016b 100644 --- a/package.json +++ b/package.json @@ -41,8 +41,7 @@ "db:migrate": "prisma migrate dev --name init", "db:push": "prisma db push", "db:seed": "ts-node -r tsconfig-paths/register prisma/seed.ts", - "prepare": "husky", - "postinstall": "npm run db:generate" + "prepare": "husky" }, "dependencies": { "@aws-sdk/client-s3": "^3.787.0", From e97a600e1eee14ca3fe89d421a4168017440ffd7 Mon Sep 17 00:00:00 2001 From: Gustavo Perdomo Date: Fri, 31 Oct 2025 00:44:05 -0300 Subject: [PATCH 05/10] feat: prisma improvements --- .github/workflows/main.yml | 20 ++ package-lock.json | 372 +++++++++++++++++++------------------ 2 files changed, 214 insertions(+), 178 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f56dd09d..e0d18054 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -35,6 +35,19 @@ jobs: test: name: Unit Tests runs-on: ubuntu-latest + services: + postgres: + image: postgres:15 + env: + POSTGRES_PASSWORD: postgres + POSTGRES_DB: nestjs_template_test + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 steps: - name: Checkout code uses: actions/checkout@v4 @@ -48,6 +61,13 @@ jobs: - name: Install dependencies run: npm ci + - name: Setup test database + run: | + npm run db:generate + npm run db:push + env: + DATABASE_URL: postgresql://postgres:postgres@localhost:5432/nestjs_template_test + - name: Run unit tests run: npm run test:cov diff --git a/package-lock.json b/package-lock.json index 731d1f59..6a0b649c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,6 @@ "": { "name": "nestjs-template", "version": "2.0.1", - "hasInstallScript": true, "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.787.0", @@ -1674,9 +1673,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz", - "integrity": "sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", "dev": true, "license": "MIT", "dependencies": { @@ -1703,13 +1702,13 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz", - "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.6", + "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -1718,9 +1717,9 @@ } }, "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -1742,19 +1741,35 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.1.tgz", - "integrity": "sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers/node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/core": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", - "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz", + "integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1806,9 +1821,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -1837,19 +1852,22 @@ } }, "node_modules/@eslint/js": { - "version": "9.23.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.23.0.tgz", - "integrity": "sha512-35MJ8vCPU0ZMxo7zfev2pypqTwWTofFZO6m4KAtdoFhRpLJUpHTZZ+KB3C7Hb1d7bULYwO4lJXGCi5Se+8OMbw==", + "version": "9.38.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.38.0.tgz", + "integrity": "sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A==", "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, "node_modules/@eslint/object-schema": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1857,13 +1875,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", - "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.13.0", + "@eslint/core": "^0.17.0", "levn": "^0.4.1" }, "engines": { @@ -1871,9 +1889,9 @@ } }, "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", - "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1949,6 +1967,16 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@inquirer/ansi": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.1.tgz", + "integrity": "sha512-yqq0aJW/5XPhi5xOAL1xRCpe1eh8UFVgYFpFsjEqmIR8rKLyP+HINvFXwUaxYICflJrVlxnp7lLN6As735kVpw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@inquirer/checkbox": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.1.4.tgz", @@ -1997,15 +2025,15 @@ } }, "node_modules/@inquirer/core": { - "version": "10.1.9", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.9.tgz", - "integrity": "sha512-sXhVB8n20NYkUBfDYgizGHlpRVaCRjtuzNZA6xpALIUbkgfd2Hjz+DfEN6+h1BRnuxw0/P4jCIMjMsEOAMwAJw==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.0.tgz", + "integrity": "sha512-Uv2aPPPSK5jeCplQmQ9xadnFx2Zhj9b5Dj7bU6ZeCdDNNY11nhYy4btcSdtDguHqCT2h5oNeQTcUNSGGLA7NTA==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/figures": "^1.0.11", - "@inquirer/type": "^3.0.5", - "ansi-escapes": "^4.3.2", + "@inquirer/ansi": "^1.0.1", + "@inquirer/figures": "^1.0.14", + "@inquirer/type": "^3.0.9", "cli-width": "^4.1.0", "mute-stream": "^2.0.0", "signal-exit": "^4.1.0", @@ -2025,15 +2053,15 @@ } }, "node_modules/@inquirer/editor": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.9.tgz", - "integrity": "sha512-8HjOppAxO7O4wV1ETUlJFg6NDjp/W2NP5FB9ZPAcinAlNT4ZIWOLe2pUVwmmPRSV0NMdI5r/+lflN55AwZOKSw==", + "version": "4.2.21", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.21.tgz", + "integrity": "sha512-MjtjOGjr0Kh4BciaFShYpZ1s9400idOdvQ5D7u7lE6VztPFoyLcVNE5dXBmEEIQq5zi4B9h2kU+q7AVBxJMAkQ==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.9", - "@inquirer/type": "^3.0.5", - "external-editor": "^3.1.0" + "@inquirer/core": "^10.3.0", + "@inquirer/external-editor": "^1.0.2", + "@inquirer/type": "^3.0.9" }, "engines": { "node": ">=18" @@ -2070,10 +2098,49 @@ } } }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.2.tgz", + "integrity": "sha512-yy9cOoBnx58TlsPrIxauKIFQTiyH+0MK4e97y4sV9ERbI+zDxw7i2hxHLCIEGIE/8PPvDxGhgzIOTSOWcs6/MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^2.1.0", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor/node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/@inquirer/figures": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.11.tgz", - "integrity": "sha512-eOg92lvrn/aRUqbxRyvpEWnrvRuTYRifixHkYVpJiygTgVSBIHDqLh0SrMQXkafvULg3ck11V7xvR+zcgvpHFw==", + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.14.tgz", + "integrity": "sha512-DbFgdt+9/OZYFM+19dbpXOSeAstPy884FPy1KjDu4anWwymZeOYhMY1mdFri172htv6mvc/uvIAAi7b7tvjJBQ==", "dev": true, "license": "MIT", "engines": { @@ -2250,9 +2317,9 @@ } }, "node_modules/@inquirer/type": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.5.tgz", - "integrity": "sha512-ZJpeIYYueOz/i/ONzrfof8g89kNdO2hjGuvULROo3O8rlB2CRtSseE5KeirnyE4t/thAn/EwvS/vuQeJCn+NZg==", + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.9.tgz", + "integrity": "sha512-QPaNt/nmE2bLGQa9b7wwyRJoLZ7pN6rcyXvzU0YCmivmJyq1BVo94G98tStRWkoD1RgDX5C+dPlhhHzNdu/W/w==", "dev": true, "license": "MIT", "engines": { @@ -2679,9 +2746,9 @@ } }, "node_modules/@jest/reporters/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -5085,9 +5152,9 @@ } }, "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", "peer": true, @@ -5645,9 +5712,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5952,9 +6019,9 @@ } }, "node_modules/chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", + "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", "dev": true, "license": "MIT" }, @@ -6951,34 +7018,33 @@ } }, "node_modules/eslint": { - "version": "9.23.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.23.0.tgz", - "integrity": "sha512-jV7AbNoFPAY1EkFYpLq5bslU9NLNO8xnEeQXwErNibVryjk67wHVmddTBilc5srIttJDBrB0eMHKZBFbSIABCw==", + "version": "9.38.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.38.0.tgz", + "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.19.2", - "@eslint/config-helpers": "^0.2.0", - "@eslint/core": "^0.12.0", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.1", + "@eslint/core": "^0.16.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.23.0", - "@eslint/plugin-kit": "^0.2.7", + "@eslint/js": "9.38.0", + "@eslint/plugin-kit": "^0.4.0", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -7058,9 +7124,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -7105,9 +7171,9 @@ } }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -7116,9 +7182,9 @@ } }, "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -7149,15 +7215,15 @@ } }, "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.14.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7167,9 +7233,9 @@ } }, "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -7373,16 +7439,16 @@ } }, "node_modules/express-session": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.1.tgz", - "integrity": "sha512-a5mtTqEaZvBCL9A9aqkrtfz+3SMDhOVUnjafjo+s7A9Txkq+SVX2DLvSp1Zrv4uCXa3lMSK3viWnh9Gg07PBUA==", + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.2.tgz", + "integrity": "sha512-SZjssGQC7TzTs9rpPDuUrR23GNZ9+2+IkA/+IJWmvQilTr5OSliEHGF+D9scbIpdC6yGtTI0/VhaHoVes2AN/A==", "license": "MIT", "dependencies": { "cookie": "0.7.2", "cookie-signature": "1.0.7", "debug": "2.6.9", "depd": "~2.0.0", - "on-headers": "~1.0.2", + "on-headers": "~1.1.0", "parseurl": "~1.3.3", "safe-buffer": "5.2.1", "uid-safe": "~2.1.5" @@ -7419,34 +7485,6 @@ "devOptional": true, "license": "MIT" }, - "node_modules/external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "license": "MIT", - "dependencies": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/external-editor/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/fast-check": { "version": "3.23.2", "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", @@ -7791,9 +7829,9 @@ } }, "node_modules/fork-ts-checker-webpack-plugin/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -7879,15 +7917,16 @@ } }, "node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -8914,9 +8953,9 @@ } }, "node_modules/jake/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -9093,9 +9132,9 @@ } }, "node_modules/jest-config/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -9456,9 +9495,9 @@ } }, "node_modules/jest-runtime/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -10896,9 +10935,9 @@ "license": "MIT" }, "node_modules/nodemailer": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.0.tgz", - "integrity": "sha512-SQ3wZCExjeSatLE/HBaXS5vqUOQk6GtBdIIKxiFdmm01mOQZX/POJkO3SUX1wDiYcwUOJwT23scFSC9fY2H8IA==", + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.1.tgz", + "integrity": "sha512-Z+iLaBGVaSjbIzQ4pX6XV41HrooLsQ10ZWPUehGmuantvzWoDVBnmsdUcOIDM1t+yPor5pDhVlDESgOMEGxhHA==", "license": "MIT-0", "engines": { "node": ">=6.0.0" @@ -11015,9 +11054,9 @@ } }, "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -11113,16 +11152,6 @@ "node": ">=8" } }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -12261,9 +12290,9 @@ } }, "node_modules/rimraf/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -13424,9 +13453,9 @@ } }, "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -13499,19 +13528,6 @@ "devOptional": true, "license": "MIT" }, - "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "os-tmpdir": "~1.0.2" - }, - "engines": { - "node": ">=0.6.0" - } - }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -13999,9 +14015,9 @@ } }, "node_modules/validator": { - "version": "13.15.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.0.tgz", - "integrity": "sha512-36B2ryl4+oL5QxZ3AzD0t5SsMNGvTtQHpjgFO5tbNxfXbMFkY822ktCDe1MnlqV3301QQI9SLHDNJokDI+Z9pA==", + "version": "13.15.20", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.20.tgz", + "integrity": "sha512-KxPOq3V2LmfQPP4eqf3Mq/zrT0Dqp2Vmx2Bn285LwVahLc+CsxOM0crBHczm8ijlcjZ0Q5Xd6LW3z3odTPnlrw==", "license": "MIT", "engines": { "node": ">= 0.10" From bcd1685eb476732c3b66468e8a349e4b01945bcd Mon Sep 17 00:00:00 2001 From: Gustavo Perdomo Date: Fri, 31 Oct 2025 00:53:04 -0300 Subject: [PATCH 06/10] feat: prisma improvements --- jest.config.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/jest.config.js b/jest.config.js index adf0774a..d48a0eb4 100644 --- a/jest.config.js +++ b/jest.config.js @@ -19,14 +19,6 @@ module.exports = { ], coverageDirectory: './coverage', coverageReporters: ['text', 'lcov', 'html'], - coverageThreshold: { - global: { - branches: 80, - functions: 80, - lines: 80, - statements: 80, - }, - }, moduleNameMapper: { '^@application/(.*)$': '/src/application/$1', '^@core/(.*)$': '/src/core/$1', From 12b1cdcc69484e6fa66d27a2e72b8c5c9dffc197 Mon Sep 17 00:00:00 2001 From: Gustavo Perdomo Date: Fri, 31 Oct 2025 00:59:51 -0300 Subject: [PATCH 07/10] feat: update github action versions --- .github/workflows/main.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e0d18054..4211959a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -15,10 +15,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' @@ -50,10 +50,10 @@ jobs: - 5432:5432 steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' @@ -72,7 +72,7 @@ jobs: run: npm run test:cov - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v5 with: file: ./coverage/lcov.info flags: unittests @@ -84,10 +84,10 @@ jobs: needs: [lint, test] steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' @@ -99,7 +99,7 @@ jobs: run: npm run build - name: Upload build artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v5 with: name: dist path: dist/ @@ -124,10 +124,10 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' @@ -154,10 +154,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' From 26801766eb06fab975c36cfeec18a77612813e43 Mon Sep 17 00:00:00 2001 From: Gustavo Perdomo Date: Fri, 31 Oct 2025 01:04:48 -0300 Subject: [PATCH 08/10] feat: prisma improvements --- .github/workflows/main.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4211959a..e4d500bf 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -95,6 +95,12 @@ jobs: - name: Install dependencies run: npm ci + - name: Setup test database + run: | + npm run db:generate + env: + DATABASE_URL: postgresql://postgres:postgres@localhost:5432/nestjs_template_test + - name: Build application run: npm run build From 8fe375baf1ca662bff1ad8b945a4ecc3abca8fb3 Mon Sep 17 00:00:00 2001 From: Gustavo Perdomo Date: Fri, 31 Oct 2025 01:30:35 -0300 Subject: [PATCH 09/10] feat: prisma improvements --- package-lock.json | 39 +++++++++++++++++++++++++++++++++++++++ package.json | 1 + prisma.config.ts | 1 - prisma/seed.ts | 5 ++++- test/jest-e2e.json | 3 ++- 5 files changed, 46 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6a0b649c..b4f89578 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,6 +55,7 @@ "@types/passport-jwt": "^4.0.1", "@types/qrcode": "^1.5.5", "@types/speakeasy": "^2.0.10", + "@types/supertest": "6.0.3", "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^8.29.0", "@typescript-eslint/parser": "^8.29.0", @@ -4460,6 +4461,13 @@ "@types/node": "*" } }, + "node_modules/@types/cookiejar": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", + "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/eslint": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", @@ -4593,6 +4601,13 @@ "@types/node": "*" } }, + "node_modules/@types/methods": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", + "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -4720,6 +4735,30 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/superagent": { + "version": "8.1.9", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.9.tgz", + "integrity": "sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/cookiejar": "^2.1.5", + "@types/methods": "^1.1.4", + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/supertest": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.3.tgz", + "integrity": "sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/methods": "^1.1.4", + "@types/superagent": "^8.1.0" + } + }, "node_modules/@types/uuid": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", diff --git a/package.json b/package.json index 87b2016b..4e7b4eee 100644 --- a/package.json +++ b/package.json @@ -90,6 +90,7 @@ "@types/passport-jwt": "^4.0.1", "@types/qrcode": "^1.5.5", "@types/speakeasy": "^2.0.10", + "@types/supertest": "6.0.3", "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^8.29.0", "@typescript-eslint/parser": "^8.29.0", diff --git a/prisma.config.ts b/prisma.config.ts index 9e2a3a3e..51d7419c 100644 --- a/prisma.config.ts +++ b/prisma.config.ts @@ -1,4 +1,3 @@ -import 'dotenv/config'; import { defineConfig, env } from 'prisma/config'; export default defineConfig({ diff --git a/prisma/seed.ts b/prisma/seed.ts index 00b00127..17224b16 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -1,4 +1,5 @@ import { PrismaClient } from '@generated/prisma/client'; +import { PrismaPg } from '@prisma/adapter-pg'; import * as bcrypt from 'bcrypt'; import { ActionType, ResourceType } from '../src/core/value-objects/resource-action.vo'; @@ -132,7 +133,9 @@ async function hashPassword(password: string): Promise { return bcrypt.hash(password, salt); } -const prisma = new PrismaClient(); +const connectionString = `${process.env.DATABASE_URL}`; +const adapter = new PrismaPg({ connectionString }); +const prisma = new PrismaClient({ adapter }); async function main() { console.log('Seeding database...'); diff --git a/test/jest-e2e.json b/test/jest-e2e.json index 8f32b5dc..1a982e39 100644 --- a/test/jest-e2e.json +++ b/test/jest-e2e.json @@ -9,8 +9,9 @@ "^.+\\.(t|j)s$": "ts-jest" }, "moduleNameMapper": { - "^@core/(.*)$": "/../src/core/$1", "^@application/(.*)$": "/../src/application/$1", + "^@core/(.*)$": "/../src/core/$1", + "^@generated/(.*)$": "/../generated/$1", "^@infrastructure/(.*)$": "/../src/infrastructure/$1", "^@presentation/(.*)$": "/../src/presentation/$1", "^@shared/(.*)$": "/../src/shared/$1" From 0afcdc867d48cfb84a83fef86f72c49c4b4fde34 Mon Sep 17 00:00:00 2001 From: Gustavo Perdomo Date: Fri, 31 Oct 2025 00:23:10 -0300 Subject: [PATCH 10/10] feat: reformat code --- .prettierrc | 16 ++------- .../commands/admin/change-password.command.ts | 4 +-- .../commands/auth/login.command.spec.ts | 31 ++++------------ .../commands/auth/login.command.ts | 4 +-- .../auth/refresh-token.command.spec.ts | 25 ++++--------- .../commands/auth/refresh-token.command.ts | 4 +-- .../auth/register-user.command.spec.ts | 16 ++------- .../auth/request-password-reset.command.ts | 7 ++-- .../commands/auth/reset-password.command.ts | 6 +--- .../auth/send-verification-email.command.ts | 4 +-- .../commands/auth/verify-email.command.ts | 4 +-- .../commands/auth/verify-otp.command.ts | 2 +- .../commands/role/create-role.command.ts | 7 +--- .../commands/user/update-user.command.ts | 9 +---- .../dtos/requests/auth/register.request.ts | 11 +----- .../dtos/requests/role/create-role.request.ts | 11 +----- src/application/dtos/responses/auth/index.ts | 11 ++---- src/application/mappers/role.mapper.ts | 2 +- src/application/mappers/user.mapper.ts | 11 ++---- .../permission/get-permissions.query.ts | 2 +- .../queries/role/get-roles.query.ts | 2 +- .../queries/user/get-users.query.ts | 2 +- src/core/entities/permission.entity.ts | 12 ++----- src/core/entities/role.entity.ts | 24 ++++--------- src/core/entities/user.entity.ts | 13 +++---- src/core/exceptions/domain-exceptions.ts | 5 +-- src/core/services/auth.service.ts | 5 +-- src/core/services/health.service.ts | 23 ++++-------- src/core/services/role.service.ts | 7 +--- src/core/services/storage.service.ts | 5 +-- .../services/user-authorization.service.ts | 5 +-- src/core/services/user.service.spec.ts | 19 +++------- src/core/services/user.service.ts | 25 +++---------- .../specifications/role.specifications.ts | 5 +-- .../specifications/user.specifications.ts | 2 +- .../collections/permissions.collection.ts | 32 ++++++++--------- .../collections/roles.collection.ts | 32 ++++++++--------- src/core/value-objects/resource-action.vo.ts | 3 +- src/core/value-objects/token.vo.spec.ts | 8 ++--- src/infrastructure/config/configuration.ts | 4 +-- .../database/prisma/prisma.service.ts | 4 +-- .../logger/logger.service.spec.ts | 2 +- src/infrastructure/logger/logger.service.ts | 14 ++------ .../repositories/base.repository.ts | 6 +--- .../repositories/file.repository.ts | 4 +-- .../repositories/password-reset.repository.ts | 5 +-- .../repositories/permission.repository.ts | 9 ++--- .../repositories/refresh-token.repository.ts | 12 ++----- .../repositories/role.repository.ts | 13 +++---- .../repositories/user.repository.spec.ts | 7 ++-- .../repositories/user.repository.ts | 19 +++++----- .../services/throttler.service.spec.ts | 21 +++-------- .../services/throttler.service.ts | 15 ++------ .../storage/providers/minio.provider.ts | 2 +- .../storage/providers/s3.provider.ts | 7 +--- src/main.ts | 11 ++---- .../filters/all-exceptions.filter.ts | 9 +---- src/presentation/guards/throttler.guard.ts | 7 ++-- .../interceptors/logging.interceptor.ts | 2 +- .../interceptors/transform.interceptor.ts | 2 +- .../modules/admin/admin-health.controller.ts | 7 +--- .../modules/admin/admin-role.controller.ts | 30 +++------------- .../modules/admin/admin-user.controller.ts | 35 ++++--------------- .../modules/admin/admin.module.ts | 8 +---- .../modules/auth/auth.controller.ts | 7 ++-- .../modules/auth/providers/email.provider.ts | 5 +-- .../modules/auth/providers/token.provider.ts | 2 +- .../modules/storage/storage.controller.ts | 14 ++------ .../modules/storage/storage.module.ts | 6 +--- .../modules/user/user.controller.ts | 28 +++------------ src/presentation/modules/user/user.module.ts | 6 +--- src/test/mocks/repositories.factory.ts | 4 +-- src/test/mocks/repositories.mock.ts | 32 ++++++++--------- test/auth.e2e-spec.ts | 17 ++++----- test/role.e2e-spec.ts | 16 +++------ test/user.e2e-spec.ts | 4 +-- 76 files changed, 216 insertions(+), 596 deletions(-) diff --git a/.prettierrc b/.prettierrc index 8937527c..fda6c009 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,17 +1,5 @@ { + "printWidth": 120, "singleQuote": true, - "trailingComma": "all", - "printWidth": 100, - "tabWidth": 2, - "semi": true, - "bracketSpacing": true, - "arrowParens": "avoid", - "endOfLine": "lf", - "useTabs": false, - "quoteProps": "as-needed", - "jsxSingleQuote": false, - "bracketSameLine": false, - "proseWrap": "preserve", - "htmlWhitespaceSensitivity": "css", - "embeddedLanguageFormatting": "auto" + "trailingComma": "all" } \ No newline at end of file diff --git a/src/application/commands/admin/change-password.command.ts b/src/application/commands/admin/change-password.command.ts index 51871f58..eb56daa1 100644 --- a/src/application/commands/admin/change-password.command.ts +++ b/src/application/commands/admin/change-password.command.ts @@ -14,9 +14,7 @@ export class AdminChangePasswordCommand extends Command { @Injectable() @CommandHandler(AdminChangePasswordCommand) -export class AdminChangePasswordCommandHandler - implements ICommandHandler -{ +export class AdminChangePasswordCommandHandler implements ICommandHandler { constructor( private readonly userService: UserService, @Inject(LoggerService) private readonly logger: LoggerService, diff --git a/src/application/commands/auth/login.command.spec.ts b/src/application/commands/auth/login.command.spec.ts index 8d739a20..a53941a5 100644 --- a/src/application/commands/auth/login.command.spec.ts +++ b/src/application/commands/auth/login.command.spec.ts @@ -59,12 +59,7 @@ const mockLoggerService = { // Create utility functions for test data const createTestUser = (): User => { - const user = User.create( - new Email('test@example.com'), - 'hashedPassword', - new FirstName('John'), - new LastName('Doe'), - ); + const user = User.create(new Email('test@example.com'), 'hashedPassword', new FirstName('John'), new LastName('Doe')); // Add roles const role = Role.fromData({ @@ -141,7 +136,7 @@ describe('LoginCommandHandler', () => { firstName: user.firstName.getValue(), lastName: user.lastName.getValue(), emailVerified: emailVerified || false, - roles: user.roles.map(role => ({ + roles: user.roles.map((role) => ({ id: role.id.getValue(), name: role.name, })), @@ -164,10 +159,7 @@ describe('LoginCommandHandler', () => { // Act & Assert await expect(handler.execute(command)).rejects.toThrow(UnauthorizedException); - expect(userService.validateCredentials).toHaveBeenCalledWith( - 'test@example.com', - 'wrongPassword', - ); + expect(userService.validateCredentials).toHaveBeenCalledWith('test@example.com', 'wrongPassword'); }); it('should return email verification required response when email is not verified', async () => { @@ -193,10 +185,7 @@ describe('LoginCommandHandler', () => { message: 'Email verification required', }); - expect(userService.validateCredentials).toHaveBeenCalledWith( - 'test@example.com', - 'Password123!', - ); + expect(userService.validateCredentials).toHaveBeenCalledWith('test@example.com', 'Password123!'); expect(authService.updateLastLogin).toHaveBeenCalledWith(user.id.getValue()); expect(authService.isEmailVerified).toHaveBeenCalledWith('test@example.com'); }); @@ -225,10 +214,7 @@ describe('LoginCommandHandler', () => { message: 'OTP verification required', }); - expect(userService.validateCredentials).toHaveBeenCalledWith( - 'test@example.com', - 'Password123!', - ); + expect(userService.validateCredentials).toHaveBeenCalledWith('test@example.com', 'Password123!'); expect(authService.updateLastLogin).toHaveBeenCalledWith(user.id.getValue()); expect(authService.isEmailVerified).toHaveBeenCalledWith('test@example.com'); }); @@ -266,10 +252,7 @@ describe('LoginCommandHandler', () => { }), }); - expect(userService.validateCredentials).toHaveBeenCalledWith( - 'test@example.com', - 'Password123!', - ); + expect(userService.validateCredentials).toHaveBeenCalledWith('test@example.com', 'Password123!'); expect(authService.updateLastLogin).toHaveBeenCalledWith(user.id.getValue()); expect(authService.isEmailVerified).toHaveBeenCalledWith('test@example.com'); expect(roleRepository.findById).toHaveBeenCalledWith(user.roles[0].id.getValue()); @@ -328,7 +311,7 @@ describe('LoginCommandHandler', () => { mockAuthService.isEmailVerified.mockResolvedValue(true); // Mock repository to return different roles based on role id - mockRoleRepository.findById.mockImplementation(roleId => { + mockRoleRepository.findById.mockImplementation((roleId) => { if (roleId === '550e8400-e29b-41d4-a716-446655440001') { return Promise.resolve(userRoleWithPermissions); } else if (roleId === '550e8400-e29b-41d4-a716-446655440003') { diff --git a/src/application/commands/auth/login.command.ts b/src/application/commands/auth/login.command.ts index 3c640e12..a9448b39 100644 --- a/src/application/commands/auth/login.command.ts +++ b/src/application/commands/auth/login.command.ts @@ -97,7 +97,7 @@ export class LoginCommandHandler implements ICommandHandler { for (const role of user.roles) { const roleWithPermissions = await this.roleRepository.findById(role.id.getValue()); if (roleWithPermissions && roleWithPermissions.permissions) { - roleWithPermissions.permissions.forEach(permission => { + roleWithPermissions.permissions.forEach((permission) => { userPermissions.add(permission.getStringName()); }); } @@ -106,7 +106,7 @@ export class LoginCommandHandler implements ICommandHandler { this.logger.debug({ message: 'User permissions collected', userId: user.id.getValue(), - roles: user.roles.map(r => r.name), + roles: user.roles.map((r) => r.name), permissionsCount: userPermissions.size, }); diff --git a/src/application/commands/auth/refresh-token.command.spec.ts b/src/application/commands/auth/refresh-token.command.spec.ts index 55630fe8..78e30cec 100644 --- a/src/application/commands/auth/refresh-token.command.spec.ts +++ b/src/application/commands/auth/refresh-token.command.spec.ts @@ -66,12 +66,7 @@ const mockLoggerService = { // Create test data const createTestUser = (): User => { - const user = User.create( - new Email('test@example.com'), - 'hashedPassword', - new FirstName('John'), - new LastName('Doe'), - ); + const user = User.create(new Email('test@example.com'), 'hashedPassword', new FirstName('John'), new LastName('Doe')); // Add role - need to use fromData method to set ID const role = Role.fromData({ @@ -181,13 +176,9 @@ describe('RefreshTokenCommandHandler', () => { userId: '550e8400-e29b-41d4-a716-446655440010', }); - expect(authService.validateRefreshToken).toHaveBeenCalledWith( - '550e8400-e29b-41d4-a716-446655440005', - ); + expect(authService.validateRefreshToken).toHaveBeenCalledWith('550e8400-e29b-41d4-a716-446655440005'); expect(userRepository.findById).toHaveBeenCalledWith('550e8400-e29b-41d4-a716-446655440000'); - expect(authService.revokeRefreshToken).toHaveBeenCalledWith( - '550e8400-e29b-41d4-a716-446655440005', - ); + expect(authService.revokeRefreshToken).toHaveBeenCalledWith('550e8400-e29b-41d4-a716-446655440005'); expect(roleRepository.findById).toHaveBeenCalledWith('550e8400-e29b-41d4-a716-446655440001'); expect(authService.isEmailVerified).toHaveBeenCalledWith('test@example.com'); @@ -222,9 +213,7 @@ describe('RefreshTokenCommandHandler', () => { // Act & Assert await expect(handler.execute(command)).rejects.toThrow(UnauthorizedException); - expect(authService.validateRefreshToken).toHaveBeenCalledWith( - '550e8400-e29b-41d4-a716-446655440006', - ); + expect(authService.validateRefreshToken).toHaveBeenCalledWith('550e8400-e29b-41d4-a716-446655440006'); expect(userRepository.findById).not.toHaveBeenCalled(); }); @@ -241,9 +230,7 @@ describe('RefreshTokenCommandHandler', () => { // Act & Assert await expect(handler.execute(command)).rejects.toThrow(UnauthorizedException); - expect(authService.validateRefreshToken).toHaveBeenCalledWith( - '550e8400-e29b-41d4-a716-446655440005', - ); + expect(authService.validateRefreshToken).toHaveBeenCalledWith('550e8400-e29b-41d4-a716-446655440005'); expect(userRepository.findById).toHaveBeenCalledWith('550e8400-e29b-41d4-a716-446655440000'); expect(authService.revokeRefreshToken).not.toHaveBeenCalled(); }); @@ -300,7 +287,7 @@ describe('RefreshTokenCommandHandler', () => { mockAuthService.isEmailVerified.mockResolvedValue(true); // Mock repository to return different roles based on role id - mockRoleRepository.findById.mockImplementation(roleId => { + mockRoleRepository.findById.mockImplementation((roleId) => { if (roleId === '550e8400-e29b-41d4-a716-446655440001') { return Promise.resolve(userRoleWithPermissions); } else if (roleId === '550e8400-e29b-41d4-a716-446655440003') { diff --git a/src/application/commands/auth/refresh-token.command.ts b/src/application/commands/auth/refresh-token.command.ts index e6ae15dd..14614048 100644 --- a/src/application/commands/auth/refresh-token.command.ts +++ b/src/application/commands/auth/refresh-token.command.ts @@ -51,7 +51,7 @@ export class RefreshTokenCommandHandler implements ICommandHandler { + roleWithPermissions.permissions.forEach((permission) => { userPermissions.add(permission.getStringName()); }); } @@ -65,7 +65,7 @@ export class RefreshTokenCommandHandler implements ICommandHandler role.name), + roles: user.roles.map((role) => role.name), permissions: Array.from(userPermissions), }; diff --git a/src/application/commands/auth/register-user.command.spec.ts b/src/application/commands/auth/register-user.command.spec.ts index 7d14f9cc..36996796 100644 --- a/src/application/commands/auth/register-user.command.spec.ts +++ b/src/application/commands/auth/register-user.command.spec.ts @@ -43,7 +43,7 @@ describe('RegisterUserCommandHandler', () => { userService = module.get(UserService); // Mock UserMapper - jest.spyOn(UserMapper, 'toBaseResponse').mockImplementation(user => { + jest.spyOn(UserMapper, 'toBaseResponse').mockImplementation((user) => { return { id: user.id.getValue(), email: user.email.getValue(), @@ -86,12 +86,7 @@ describe('RegisterUserCommandHandler', () => { lastName: createdUser.lastName.getValue(), }); - expect(userService.createUser).toHaveBeenCalledWith( - 'new@example.com', - 'Password123!', - 'New', - 'User', - ); + expect(userService.createUser).toHaveBeenCalledWith('new@example.com', 'Password123!', 'New', 'User'); expect(UserMapper.toBaseResponse).toHaveBeenCalledWith(createdUser); }); @@ -111,11 +106,6 @@ describe('RegisterUserCommandHandler', () => { // Act & Assert await expect(handler.execute(command)).rejects.toThrow(error); - expect(userService.createUser).toHaveBeenCalledWith( - 'existing@example.com', - 'Password123!', - 'Existing', - 'User', - ); + expect(userService.createUser).toHaveBeenCalledWith('existing@example.com', 'Password123!', 'Existing', 'User'); }); }); diff --git a/src/application/commands/auth/request-password-reset.command.ts b/src/application/commands/auth/request-password-reset.command.ts index db33eb75..88776254 100644 --- a/src/application/commands/auth/request-password-reset.command.ts +++ b/src/application/commands/auth/request-password-reset.command.ts @@ -13,9 +13,7 @@ export class RequestPasswordResetCommand extends Command<{ message: string }> { @Injectable() @CommandHandler(RequestPasswordResetCommand) -export class RequestPasswordResetCommandHandler - implements ICommandHandler -{ +export class RequestPasswordResetCommandHandler implements ICommandHandler { constructor( private readonly authService: AuthService, private readonly emailProvider: EmailProvider, @@ -36,8 +34,7 @@ export class RequestPasswordResetCommandHandler if (error instanceof EntityNotFoundException) { // For security reasons, we don't want to reveal whether an email exists in our system return { - message: - 'If your email exists in our system, you will receive a password reset link shortly', + message: 'If your email exists in our system, you will receive a password reset link shortly', }; } throw error; diff --git a/src/application/commands/auth/reset-password.command.ts b/src/application/commands/auth/reset-password.command.ts index 65939bfc..1c121826 100644 --- a/src/application/commands/auth/reset-password.command.ts +++ b/src/application/commands/auth/reset-password.command.ts @@ -1,9 +1,5 @@ import { ResetPasswordRequest } from '@application/dtos'; -import { - EntityNotFoundException, - OtpExpiredException, - OtpInvalidException, -} from '@core/exceptions/domain-exceptions'; +import { EntityNotFoundException, OtpExpiredException, OtpInvalidException } from '@core/exceptions/domain-exceptions'; import { AuthService } from '@core/services/auth.service'; import { UserService } from '@core/services/user.service'; import { BadRequestException, Injectable, UnauthorizedException } from '@nestjs/common'; diff --git a/src/application/commands/auth/send-verification-email.command.ts b/src/application/commands/auth/send-verification-email.command.ts index 53608b38..008b3a30 100644 --- a/src/application/commands/auth/send-verification-email.command.ts +++ b/src/application/commands/auth/send-verification-email.command.ts @@ -12,9 +12,7 @@ export class SendVerificationEmailCommand extends Command<{ message: string }> { @Injectable() @CommandHandler(SendVerificationEmailCommand) -export class SendVerificationEmailCommandHandler - implements ICommandHandler -{ +export class SendVerificationEmailCommandHandler implements ICommandHandler { constructor( private readonly authService: AuthService, private readonly emailProvider: EmailProvider, diff --git a/src/application/commands/auth/verify-email.command.ts b/src/application/commands/auth/verify-email.command.ts index 7549edb4..123cd935 100644 --- a/src/application/commands/auth/verify-email.command.ts +++ b/src/application/commands/auth/verify-email.command.ts @@ -54,7 +54,7 @@ export class VerifyEmailCommandHandler implements ICommandHandler { + roleWithPermissions.permissions.forEach((permission) => { userPermissions.add(permission.getStringName()); }); } @@ -65,7 +65,7 @@ export class VerifyEmailCommandHandler implements ICommandHandler role.name), + roles: user.roles.map((role) => role.name), permissions: Array.from(userPermissions), }; diff --git a/src/application/commands/auth/verify-otp.command.ts b/src/application/commands/auth/verify-otp.command.ts index cdd2cbf1..96da2aa9 100644 --- a/src/application/commands/auth/verify-otp.command.ts +++ b/src/application/commands/auth/verify-otp.command.ts @@ -48,7 +48,7 @@ export class VerifyOtpCommandHandler implements ICommandHandler role.name), + roles: user.roles.map((role) => role.name), }; const accessToken = this.jwtService.sign(payload, { diff --git a/src/application/commands/role/create-role.command.ts b/src/application/commands/role/create-role.command.ts index be658d15..be6e667e 100644 --- a/src/application/commands/role/create-role.command.ts +++ b/src/application/commands/role/create-role.command.ts @@ -33,12 +33,7 @@ export class CreateRoleCommandHandler implements ICommandHandler 0) { - role = await this.roleService.createRoleWithPermissions( - name, - description, - permissionIds, - isDefault, - ); + role = await this.roleService.createRoleWithPermissions(name, description, permissionIds, isDefault); } else { // Create the role without permissions role = await this.roleService.createRole(name, description, isDefault); diff --git a/src/application/commands/user/update-user.command.ts b/src/application/commands/user/update-user.command.ts index cff308e3..b8a1f94b 100644 --- a/src/application/commands/user/update-user.command.ts +++ b/src/application/commands/user/update-user.command.ts @@ -23,14 +23,7 @@ export class UpdateUserCommandHandler implements ICommandHandler this.toPermissionResponse(permission)) || [], + permissions: role.permissions?.map((permission) => this.toPermissionResponse(permission)) || [], createdAt: role.createdAt, updatedAt: role.updatedAt, }; diff --git a/src/application/mappers/user.mapper.ts b/src/application/mappers/user.mapper.ts index 003e9c03..3f2bea69 100644 --- a/src/application/mappers/user.mapper.ts +++ b/src/application/mappers/user.mapper.ts @@ -1,11 +1,6 @@ import { User } from '@core/entities/user.entity'; import { Role } from '@core/entities/role.entity'; -import { - UserBaseResponse, - UserDetailResponse, - UserAuthResponse, - UserRoleDetailResponse, -} from '@application/dtos'; +import { UserBaseResponse, UserDetailResponse, UserAuthResponse, UserRoleDetailResponse } from '@application/dtos'; export class UserMapper { /** @@ -42,7 +37,7 @@ export class UserMapper { isActive: user.isActive, otpEnabled: user.otpEnabled, lastLoginAt: user.lastLoginAt, - roles: user.roles?.map(role => this.toRoleResponse(role)) || [], + roles: user.roles?.map((role) => this.toRoleResponse(role)) || [], createdAt: user.createdAt, updatedAt: user.updatedAt, }; @@ -54,7 +49,7 @@ export class UserMapper { static toAuthResponse(user: User, emailVerified: boolean = false): UserAuthResponse { return { ...this.toBaseResponse(user, emailVerified), - roles: user.roles?.map(role => this.toRoleResponse(role)) || [], + roles: user.roles?.map((role) => this.toRoleResponse(role)) || [], }; } } diff --git a/src/application/queries/permission/get-permissions.query.ts b/src/application/queries/permission/get-permissions.query.ts index 14769b2c..fbd0de3a 100644 --- a/src/application/queries/permission/get-permissions.query.ts +++ b/src/application/queries/permission/get-permissions.query.ts @@ -17,6 +17,6 @@ export class GetPermissionsQueryHandler implements IQueryHandler PermissionResponse.fromEntity(permission)); + return permissions.map((permission) => PermissionResponse.fromEntity(permission)); } } diff --git a/src/application/queries/role/get-roles.query.ts b/src/application/queries/role/get-roles.query.ts index 444f1944..b8791498 100644 --- a/src/application/queries/role/get-roles.query.ts +++ b/src/application/queries/role/get-roles.query.ts @@ -18,6 +18,6 @@ export class GetRolesQueryHandler implements IQueryHandler { const roles = await this.roleRepository.findAll(); // Use the mapper to convert each role to response DTO - return roles.map(role => RoleMapper.toDetailResponse(role)); + return roles.map((role) => RoleMapper.toDetailResponse(role)); } } diff --git a/src/application/queries/user/get-users.query.ts b/src/application/queries/user/get-users.query.ts index bc06fdf7..9e623b4f 100644 --- a/src/application/queries/user/get-users.query.ts +++ b/src/application/queries/user/get-users.query.ts @@ -42,7 +42,7 @@ export class GetUsersQueryHandler implements IQueryHandler { }); return { - users: users.map(user => UserMapper.toDetailResponse(user)), + users: users.map((user) => UserMapper.toDetailResponse(user)), total, page, limit, diff --git a/src/core/entities/permission.entity.ts b/src/core/entities/permission.entity.ts index b87568d0..509fa3d9 100644 --- a/src/core/entities/permission.entity.ts +++ b/src/core/entities/permission.entity.ts @@ -11,19 +11,11 @@ export class Permission { private readonly _createdAt: Date; private _updatedAt: Date; - private constructor( - id: PermissionId, - resourceAction: ResourceAction, - description: string, - createdAt?: Date, - ) { + private constructor(id: PermissionId, resourceAction: ResourceAction, description: string, createdAt?: Date) { this.validateDescription(description); this._id = id; - this._name = PermissionName.create( - resourceAction.getResource(), - resourceAction.getAction().toString(), - ); + this._name = PermissionName.create(resourceAction.getResource(), resourceAction.getAction().toString()); this._description = description; this._resourceAction = resourceAction; this._createdAt = createdAt || new Date(); diff --git a/src/core/entities/role.entity.ts b/src/core/entities/role.entity.ts index 3d933674..2c32f9c7 100644 --- a/src/core/entities/role.entity.ts +++ b/src/core/entities/role.entity.ts @@ -18,13 +18,7 @@ export class Role { private readonly _createdAt: Date; private _updatedAt: Date; - private constructor( - id: RoleId, - name: string, - description: string, - isDefault: boolean = false, - createdAt?: Date, - ) { + private constructor(id: RoleId, name: string, description: string, isDefault: boolean = false, createdAt?: Date) { this.validateName(name); this.validateDescription(description); @@ -52,13 +46,7 @@ export class Role { createdAt: Date; updatedAt: Date; }): Role { - const role = new Role( - RoleId.fromString(data.id), - data.name, - data.description, - data.isDefault, - data.createdAt, - ); + const role = new Role(RoleId.fromString(data.id), data.name, data.description, data.isDefault, data.createdAt); role._permissions = data.permissions; role._updatedAt = data.updatedAt; @@ -119,12 +107,12 @@ export class Role { } removePermission(permissionId: PermissionId): void { - const permissionExists = this._permissions.some(p => p.id.equals(permissionId)); + const permissionExists = this._permissions.some((p) => p.id.equals(permissionId)); if (!permissionExists) { return; // Permission not found, no change needed } - this._permissions = this._permissions.filter(p => !p.id.equals(permissionId)); + this._permissions = this._permissions.filter((p) => !p.id.equals(permissionId)); this._updatedAt = new Date(); } @@ -168,7 +156,7 @@ export class Role { // Query methods hasPermission(permissionId: PermissionId): boolean { - return this._permissions.some(p => p.id.equals(permissionId)); + return this._permissions.some((p) => p.id.equals(permissionId)); } hasPermissionByName(permissionName: string): boolean { @@ -196,7 +184,7 @@ export class Role { } getPermissionNames(): string[] { - return this._permissions.map(p => p.getPermissionName()); + return this._permissions.map((p) => p.getPermissionName()); } // Private validation methods diff --git a/src/core/entities/user.entity.ts b/src/core/entities/user.entity.ts index f469e1d4..0c88bb73 100644 --- a/src/core/entities/user.entity.ts +++ b/src/core/entities/user.entity.ts @@ -49,12 +49,7 @@ export class User { } // Factory method for creating new users - static create( - email: Email, - passwordHash: string, - firstName: FirstName, - lastName: LastName, - ): User { + static create(email: Email, passwordHash: string, firstName: FirstName, lastName: LastName): User { const userId = UserId.create(); const user = new User(userId, email, passwordHash, firstName, lastName); @@ -227,12 +222,12 @@ export class User { throw new UserCannotRemoveLastRoleException(); } - const roleToRemove = this._roles.find(r => r.id.equals(roleId)); + const roleToRemove = this._roles.find((r) => r.id.equals(roleId)); if (!roleToRemove) { return; // Role not found, no change needed } - this._roles = this._roles.filter(r => !r.id.equals(roleId)); + this._roles = this._roles.filter((r) => !r.id.equals(roleId)); this._updatedAt = new Date(); } @@ -293,7 +288,7 @@ export class User { // Query methods hasRole(roleId: RoleId): boolean { - return this._roles.some(r => r.id.equals(roleId)); + return this._roles.some((r) => r.id.equals(roleId)); } hasPermission(permissionName: string): boolean { diff --git a/src/core/exceptions/domain-exceptions.ts b/src/core/exceptions/domain-exceptions.ts index 6ff24028..72ff04c8 100644 --- a/src/core/exceptions/domain-exceptions.ts +++ b/src/core/exceptions/domain-exceptions.ts @@ -126,10 +126,7 @@ export class RoleHasAssignedUsersException extends RoleDomainException { export class PermissionAlreadyAssignedException extends RoleDomainException { constructor(permissionName: string, roleName: string) { - super( - `Permission ${permissionName} is already assigned to role ${roleName}`, - HttpStatus.CONFLICT, - ); + super(`Permission ${permissionName} is already assigned to role ${roleName}`, HttpStatus.CONFLICT); } } diff --git a/src/core/services/auth.service.ts b/src/core/services/auth.service.ts index 5d0dd7b1..8ca4a6f9 100644 --- a/src/core/services/auth.service.ts +++ b/src/core/services/auth.service.ts @@ -61,10 +61,7 @@ export class AuthService { private get tokenConfig() { return { - refreshExpiration: parseInt( - this.configService.get('JWT_REFRESH_EXPIRATION', '7d').replace('d', ''), - 10, - ), + refreshExpiration: parseInt(this.configService.get('JWT_REFRESH_EXPIRATION', '7d').replace('d', ''), 10), }; } diff --git a/src/core/services/health.service.ts b/src/core/services/health.service.ts index 8e9432c1..a1e7f390 100644 --- a/src/core/services/health.service.ts +++ b/src/core/services/health.service.ts @@ -86,7 +86,7 @@ export class HealthService { checkResults.database = checks[0].status === 'fulfilled'; checkResults.config = checks[1].status === 'fulfilled'; - const failedChecks = checks.filter(check => check.status === 'rejected'); + const failedChecks = checks.filter((check) => check.status === 'rejected'); if (failedChecks.length > 0) { const failures = failedChecks.map((check, _index) => @@ -176,7 +176,7 @@ export class HealthService { checks.push(memoryCheck); // Determine overall status - const errorCount = checks.filter(check => check.status === 'error').length; + const errorCount = checks.filter((check) => check.status === 'error').length; if (errorCount > 0) { overallStatus = errorCount >= checks.length / 2 ? 'down' : 'degraded'; } @@ -212,12 +212,10 @@ export class HealthService { private async checkConfiguration(): Promise { const requiredVars = ['JWT_SECRET', 'DATABASE_URL']; - const missing = requiredVars.filter(key => !this.configService.get(key)); + const missing = requiredVars.filter((key) => !this.configService.get(key)); if (missing.length > 0) { - throw new ConfigurationException( - `Missing required environment variables: ${missing.join(', ')}`, - ); + throw new ConfigurationException(`Missing required environment variables: ${missing.join(', ')}`); } } @@ -239,10 +237,7 @@ export class HealthService { } } - private async performHealthCheck( - name: string, - checkFn: () => Promise, - ): Promise { + private async performHealthCheck(name: string, checkFn: () => Promise): Promise { const startTime = Date.now(); try { @@ -275,7 +270,7 @@ export class HealthService { // CPU usage calculation (simplified) const startUsage = process.cpuUsage(); - await new Promise(resolve => setTimeout(resolve, 100)); // 100ms sampling + await new Promise((resolve) => setTimeout(resolve, 100)); // 100ms sampling const endUsage = process.cpuUsage(startUsage); // Convert microseconds to milliseconds and calculate percentage @@ -286,10 +281,6 @@ export class HealthService { } private getApplicationVersion(): string { - return ( - this.configService.get('npm_package_version') || - process.env.npm_package_version || - '1.0.0' - ); + return this.configService.get('npm_package_version') || process.env.npm_package_version || '1.0.0'; } } diff --git a/src/core/services/role.service.ts b/src/core/services/role.service.ts index 53a4f448..794458fd 100644 --- a/src/core/services/role.service.ts +++ b/src/core/services/role.service.ts @@ -86,12 +86,7 @@ export class RoleService { return this.roleRepository.create(role); } - async updateRole( - id: string, - name?: string, - description?: string, - isDefault?: boolean, - ): Promise { + async updateRole(id: string, name?: string, description?: string, isDefault?: boolean): Promise { const role = await this.roleRepository.findById(id); if (!role) { throw new EntityNotFoundException('Role', id); diff --git a/src/core/services/storage.service.ts b/src/core/services/storage.service.ts index 4f34e7bc..be6ca070 100644 --- a/src/core/services/storage.service.ts +++ b/src/core/services/storage.service.ts @@ -42,10 +42,7 @@ export class StorageService { return this.fileRepository.findByUserId(userId); } - async getAllFiles( - page: number = 1, - limit: number = 20, - ): Promise<{ files: File[]; total: number }> { + async getAllFiles(page: number = 1, limit: number = 20): Promise<{ files: File[]; total: number }> { return this.fileRepository.findAll(page, limit); } diff --git a/src/core/services/user-authorization.service.ts b/src/core/services/user-authorization.service.ts index dd2203bd..6eb8d8b0 100644 --- a/src/core/services/user-authorization.service.ts +++ b/src/core/services/user-authorization.service.ts @@ -9,10 +9,7 @@ import { CanAssignRoleSpecification, CompleteUserAccountSpecification, } from '@core/specifications/user.specifications'; -import { - AdminRoleSpecification, - CanDeleteRoleSpecification, -} from '@core/specifications/role.specifications'; +import { AdminRoleSpecification, CanDeleteRoleSpecification } from '@core/specifications/role.specifications'; /** * Domain service for user authorization and access control business logic diff --git a/src/core/services/user.service.spec.ts b/src/core/services/user.service.spec.ts index ea5d2351..24e2a66e 100644 --- a/src/core/services/user.service.spec.ts +++ b/src/core/services/user.service.spec.ts @@ -3,10 +3,7 @@ import { UserService } from './user.service'; import * as bcrypt from 'bcrypt'; // Mocks -import { - createMockUserRepository, - createMockRoleRepository, -} from '../../test/mocks/repositories.factory'; +import { createMockUserRepository, createMockRoleRepository } from '../../test/mocks/repositories.factory'; import { UserAuthorizationService } from './user-authorization.service'; // Tokens @@ -307,9 +304,7 @@ describe('UserService', () => { userRepository.findById.mockResolvedValue(null); // Act & Assert - await expect(service.updateUserDetails(userId, newFirstName)).rejects.toThrow( - EntityNotFoundException, - ); + await expect(service.updateUserDetails(userId, newFirstName)).rejects.toThrow(EntityNotFoundException); expect(userRepository.findById).toHaveBeenCalledWith(userId); expect(userRepository.update).not.toHaveBeenCalled(); }); @@ -406,9 +401,7 @@ describe('UserService', () => { userRepository.findById.mockResolvedValue(null); // Act & Assert - await expect(service.changePassword(userId, newPassword)).rejects.toThrow( - EntityNotFoundException, - ); + await expect(service.changePassword(userId, newPassword)).rejects.toThrow(EntityNotFoundException); expect(userRepository.findById).toHaveBeenCalledWith(userId); expect(userRepository.update).not.toHaveBeenCalled(); }); @@ -441,9 +434,7 @@ describe('UserService', () => { userRepository.findById.mockResolvedValue(user); // Act & Assert - await expect(service.changePassword(userId, newPassword)).rejects.toThrow( - InvalidValueObjectException, - ); + await expect(service.changePassword(userId, newPassword)).rejects.toThrow(InvalidValueObjectException); expect(userRepository.findById).toHaveBeenCalledWith(userId); expect(userRepository.update).not.toHaveBeenCalled(); }); @@ -461,7 +452,7 @@ describe('UserService', () => { userRepository.findById.mockResolvedValue(user); roleRepository.findById.mockResolvedValue(role); - userRepository.update.mockImplementationOnce(user => { + userRepository.update.mockImplementationOnce((user) => { return Promise.resolve({ ...user, roles: [{ id: roleId, name: 'user' }], diff --git a/src/core/services/user.service.ts b/src/core/services/user.service.ts index e181d73e..4dfc88ca 100644 --- a/src/core/services/user.service.ts +++ b/src/core/services/user.service.ts @@ -26,12 +26,7 @@ export class UserService { private readonly userAuthorizationService: UserAuthorizationService, ) {} - async createUser( - emailStr: string, - passwordStr: string, - firstName: string, - lastName: string, - ): Promise { + async createUser(emailStr: string, passwordStr: string, firstName: string, lastName: string): Promise { // Validate email using value object const email = new Email(emailStr); @@ -102,10 +97,7 @@ export class UserService { } // Update profile with new names if provided - user.updateProfile( - firstName ? new FirstName(firstName) : undefined, - lastName ? new LastName(lastName) : undefined, - ); + user.updateProfile(firstName ? new FirstName(firstName) : undefined, lastName ? new LastName(lastName) : undefined); if (emailStr) { // Validate email using value object @@ -124,7 +116,7 @@ export class UserService { // Handle role updates if (roleIds !== undefined) { // Get current role IDs - const currentRoleIds = user.roles.map(role => role.id.getValue()); + const currentRoleIds = user.roles.map((role) => role.id.getValue()); const newRoleIds = [...new Set(roleIds)]; // Remove duplicates // Remove roles that are no longer selected @@ -169,11 +161,7 @@ export class UserService { return this.comparePasswords(currentPassword, user.passwordHash); } - async changePassword( - userId: string, - newPasswordStr: string, - currentPassword?: string, - ): Promise { + async changePassword(userId: string, newPasswordStr: string, currentPassword?: string): Promise { const user = await this.userRepository.findById(userId); if (!user) { throw new EntityNotFoundException('User', userId); @@ -181,10 +169,7 @@ export class UserService { // If current password is provided, verify it if (currentPassword) { - const isCurrentPasswordValid = await this.comparePasswords( - currentPassword, - user.passwordHash, - ); + const isCurrentPasswordValid = await this.comparePasswords(currentPassword, user.passwordHash); if (!isCurrentPasswordValid) { throw new AuthenticationException('Current password is incorrect'); diff --git a/src/core/specifications/role.specifications.ts b/src/core/specifications/role.specifications.ts index 94507325..a7a8c2a3 100644 --- a/src/core/specifications/role.specifications.ts +++ b/src/core/specifications/role.specifications.ts @@ -110,10 +110,7 @@ export class HasMinimumPermissionsSpecification extends Specification { export class BasicUserRoleSpecification extends Specification { isSatisfiedBy(role: Role): boolean { return ( - !role.isAdminRole() && - !role.isDefault && - role.permissions.length > 0 && - role.permissions.length <= 10 // Basic roles shouldn't have too many permissions + !role.isAdminRole() && !role.isDefault && role.permissions.length > 0 && role.permissions.length <= 10 // Basic roles shouldn't have too many permissions ); } } diff --git a/src/core/specifications/user.specifications.ts b/src/core/specifications/user.specifications.ts index b86e7a91..68b524f8 100644 --- a/src/core/specifications/user.specifications.ts +++ b/src/core/specifications/user.specifications.ts @@ -25,7 +25,7 @@ export class TwoFactorEnabledSpecification extends Specification { */ export class AdminUserSpecification extends Specification { isSatisfiedBy(user: User): boolean { - return user.roles.some(role => role.isAdminRole()); + return user.roles.some((role) => role.isAdminRole()); } } diff --git a/src/core/value-objects/collections/permissions.collection.ts b/src/core/value-objects/collections/permissions.collection.ts index a2785bf7..7529a392 100644 --- a/src/core/value-objects/collections/permissions.collection.ts +++ b/src/core/value-objects/collections/permissions.collection.ts @@ -38,7 +38,7 @@ export class PermissionsCollection { * Remove a permission from the collection */ remove(permissionId: PermissionId): PermissionsCollection { - const filteredPermissions = this._permissions.filter(p => !p.id.equals(permissionId)); + const filteredPermissions = this._permissions.filter((p) => !p.id.equals(permissionId)); if (filteredPermissions.length === this._permissions.length) { // Permission not found, return same collection @@ -52,37 +52,35 @@ export class PermissionsCollection { * Check if collection contains a specific permission */ contains(permissionId: PermissionId): boolean { - return this._permissions.some(p => p.id.equals(permissionId)); + return this._permissions.some((p) => p.id.equals(permissionId)); } /** * Check if collection contains a permission by name */ containsByName(permissionName: string): boolean { - return this._permissions.some(p => p.getPermissionName() === permissionName); + return this._permissions.some((p) => p.getPermissionName() === permissionName); } /** * Get permission by ID */ getById(permissionId: PermissionId): Permission | undefined { - return this._permissions.find(p => p.id.equals(permissionId)); + return this._permissions.find((p) => p.id.equals(permissionId)); } /** * Get permission by name */ getByName(permissionName: string): Permission | undefined { - return this._permissions.find(p => p.getPermissionName() === permissionName); + return this._permissions.find((p) => p.getPermissionName() === permissionName); } /** * Filter permissions by resource */ filterByResource(resource: string): PermissionsCollection { - const filtered = this._permissions.filter( - p => p.getResource().toLowerCase() === resource.toLowerCase(), - ); + const filtered = this._permissions.filter((p) => p.getResource().toLowerCase() === resource.toLowerCase()); return new PermissionsCollection(filtered); } @@ -91,9 +89,7 @@ export class PermissionsCollection { * Filter permissions by action */ filterByAction(action: string): PermissionsCollection { - const filtered = this._permissions.filter( - p => p.getAction().toLowerCase() === action.toLowerCase(), - ); + const filtered = this._permissions.filter((p) => p.getAction().toLowerCase() === action.toLowerCase()); return new PermissionsCollection(filtered); } @@ -102,7 +98,7 @@ export class PermissionsCollection { * Get all resource names */ getResources(): string[] { - const resources = new Set(this._permissions.map(p => p.getResource())); + const resources = new Set(this._permissions.map((p) => p.getResource())); return Array.from(resources); } @@ -111,7 +107,7 @@ export class PermissionsCollection { * Get all action names */ getActions(): string[] { - const actions = new Set(this._permissions.map(p => p.getAction())); + const actions = new Set(this._permissions.map((p) => p.getAction())); return Array.from(actions); } @@ -120,7 +116,7 @@ export class PermissionsCollection { * Get all permission names */ getPermissionNames(): string[] { - return this._permissions.map(p => p.getPermissionName()); + return this._permissions.map((p) => p.getPermissionName()); } /** @@ -130,7 +126,7 @@ export class PermissionsCollection { const adminResources = ['user', 'role', 'permission', 'system']; const criticalActions = ['create', 'update', 'delete']; - return this._permissions.some(p => { + return this._permissions.some((p) => { const resource = p.getResource().toLowerCase(); const action = p.getAction().toLowerCase(); @@ -142,7 +138,7 @@ export class PermissionsCollection { * Check if collection allows access to a specific resource and action */ allowsAccess(resource: string, action: string): boolean { - return this._permissions.some(p => p.allowsAction(resource, action)); + return this._permissions.some((p) => p.allowsAction(resource, action)); } /** @@ -201,7 +197,7 @@ export class PermissionsCollection { * Get intersection with another permissions collection */ intersect(other: PermissionsCollection): PermissionsCollection { - const intersection = this._permissions.filter(p => other.contains(p.id)); + const intersection = this._permissions.filter((p) => other.contains(p.id)); return new PermissionsCollection(intersection); } @@ -214,7 +210,7 @@ export class PermissionsCollection { return false; } - return this._permissions.every(p => other.contains(p.id)); + return this._permissions.every((p) => other.contains(p.id)); } private validatePermissions(permissions: Permission[]): void { diff --git a/src/core/value-objects/collections/roles.collection.ts b/src/core/value-objects/collections/roles.collection.ts index f03511b2..ab98367f 100644 --- a/src/core/value-objects/collections/roles.collection.ts +++ b/src/core/value-objects/collections/roles.collection.ts @@ -37,7 +37,7 @@ export class RolesCollection { * Remove a role from the collection */ remove(roleId: RoleId): RolesCollection { - const filteredRoles = this._roles.filter(r => !r.id.equals(roleId)); + const filteredRoles = this._roles.filter((r) => !r.id.equals(roleId)); if (filteredRoles.length === this._roles.length) { // Role not found, return same collection @@ -51,42 +51,42 @@ export class RolesCollection { * Check if collection contains a specific role */ contains(roleId: RoleId): boolean { - return this._roles.some(r => r.id.equals(roleId)); + return this._roles.some((r) => r.id.equals(roleId)); } /** * Check if collection contains a role by name */ containsByName(roleName: string): boolean { - return this._roles.some(r => r.name.toLowerCase() === roleName.toLowerCase()); + return this._roles.some((r) => r.name.toLowerCase() === roleName.toLowerCase()); } /** * Get role by ID */ getById(roleId: RoleId): Role | undefined { - return this._roles.find(r => r.id.equals(roleId)); + return this._roles.find((r) => r.id.equals(roleId)); } /** * Get role by name */ getByName(roleName: string): Role | undefined { - return this._roles.find(r => r.name.toLowerCase() === roleName.toLowerCase()); + return this._roles.find((r) => r.name.toLowerCase() === roleName.toLowerCase()); } /** * Get the default role */ getDefaultRole(): Role | undefined { - return this._roles.find(r => r.isDefault); + return this._roles.find((r) => r.isDefault); } /** * Get all admin roles */ getAdminRoles(): RolesCollection { - const adminRoles = this._roles.filter(r => r.isAdminRole()); + const adminRoles = this._roles.filter((r) => r.isAdminRole()); return new RolesCollection(adminRoles); } @@ -95,7 +95,7 @@ export class RolesCollection { * Get all non-admin roles */ getNonAdminRoles(): RolesCollection { - const nonAdminRoles = this._roles.filter(r => !r.isAdminRole()); + const nonAdminRoles = this._roles.filter((r) => !r.isAdminRole()); return new RolesCollection(nonAdminRoles); } @@ -104,7 +104,7 @@ export class RolesCollection { * Get roles that can be deleted */ getDeletableRoles(): RolesCollection { - const deletableRoles = this._roles.filter(r => r.canBeDeleted()); + const deletableRoles = this._roles.filter((r) => r.canBeDeleted()); return new RolesCollection(deletableRoles); } @@ -113,14 +113,14 @@ export class RolesCollection { * Get all role names */ getRoleNames(): string[] { - return this._roles.map(r => r.name); + return this._roles.map((r) => r.name); } /** * Get all permissions from all roles (combined) */ getAllPermissions(): PermissionsCollection { - const allPermissions = this._roles.flatMap(r => r.permissions); + const allPermissions = this._roles.flatMap((r) => r.permissions); return PermissionsCollection.create(allPermissions); } @@ -129,21 +129,21 @@ export class RolesCollection { * Check if collection has admin privileges */ hasAdminPrivileges(): boolean { - return this._roles.some(r => r.isAdminRole()); + return this._roles.some((r) => r.isAdminRole()); } /** * Check if collection allows access to a specific resource and action */ allowsAccess(resource: string, action: string): boolean { - return this._roles.some(r => r.permissions.some(p => p.allowsAction(resource, action))); + return this._roles.some((r) => r.permissions.some((p) => p.allowsAction(resource, action))); } /** * Check if collection has a specific permission by name */ hasPermission(permissionName: string): boolean { - return this._roles.some(r => r.hasPermissionByName(permissionName)); + return this._roles.some((r) => r.hasPermissionByName(permissionName)); } /** @@ -241,7 +241,7 @@ export class RolesCollection { * Get intersection with another roles collection */ intersect(other: RolesCollection): RolesCollection { - const intersection = this._roles.filter(r => other.contains(r.id)); + const intersection = this._roles.filter((r) => other.contains(r.id)); return new RolesCollection(intersection); } @@ -254,7 +254,7 @@ export class RolesCollection { return false; } - return this._roles.every(r => other.contains(r.id)); + return this._roles.every((r) => other.contains(r.id)); } /** diff --git a/src/core/value-objects/resource-action.vo.ts b/src/core/value-objects/resource-action.vo.ts index a96c1155..c94548d0 100644 --- a/src/core/value-objects/resource-action.vo.ts +++ b/src/core/value-objects/resource-action.vo.ts @@ -19,8 +19,7 @@ export class ResourceAction { private readonly action: ActionType; constructor(resource: ResourceType | string, action: ActionType | string) { - const resourceValue = - typeof resource === 'string' ? this.parseResourceType(resource) : resource; + const resourceValue = typeof resource === 'string' ? this.parseResourceType(resource) : resource; if (!this.isValidResource(resourceValue)) { throw new InvalidValueObjectException('Invalid resource name'); diff --git a/src/core/value-objects/token.vo.spec.ts b/src/core/value-objects/token.vo.spec.ts index fd21614a..f0f323ba 100644 --- a/src/core/value-objects/token.vo.spec.ts +++ b/src/core/value-objects/token.vo.spec.ts @@ -21,9 +21,7 @@ describe('Token Value Object', () => { it('should throw for invalid token format', () => { // Arrange & Act & Assert expect(() => new Token('invalid-token')).toThrow(InvalidValueObjectException); - expect(() => new Token('550e8400-e29b-41d4-a716-446655440005-invalid')).toThrow( - InvalidValueObjectException, - ); + expect(() => new Token('550e8400-e29b-41d4-a716-446655440005-invalid')).toThrow(InvalidValueObjectException); expect(() => new Token('')).toThrow(InvalidValueObjectException); expect(() => new Token(' ')).toThrow(InvalidValueObjectException); }); @@ -53,8 +51,6 @@ describe('Token Value Object', () => { // Assert expect(token).toBeDefined(); - expect(token.getValue()).toMatch( - /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i, - ); + expect(token.getValue()).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i); }); }); diff --git a/src/infrastructure/config/configuration.ts b/src/infrastructure/config/configuration.ts index 65379649..e3bb0214 100644 --- a/src/infrastructure/config/configuration.ts +++ b/src/infrastructure/config/configuration.ts @@ -40,8 +40,6 @@ export default () => ({ i18n: { defaultLocale: process.env.DEFAULT_LOCALE || 'en', fallbackLocale: process.env.FALLBACK_LOCALE || 'en', - supportedLocales: process.env.SUPPORTED_LOCALES - ? process.env.SUPPORTED_LOCALES.split(',') - : ['en', 'ar'], + supportedLocales: process.env.SUPPORTED_LOCALES ? process.env.SUPPORTED_LOCALES.split(',') : ['en', 'ar'], }, }); diff --git a/src/infrastructure/database/prisma/prisma.service.ts b/src/infrastructure/database/prisma/prisma.service.ts index eb61148e..7745d90a 100644 --- a/src/infrastructure/database/prisma/prisma.service.ts +++ b/src/infrastructure/database/prisma/prisma.service.ts @@ -28,10 +28,10 @@ export class PrismaService extends PrismaClient implements OnModuleInit, OnModul } // Only for testing purposes - const models = Reflect.ownKeys(this).filter(key => key[0] !== '_' && key[0] !== '$'); + const models = Reflect.ownKeys(this).filter((key) => key[0] !== '_' && key[0] !== '$'); return Promise.all( - models.map(modelKey => { + models.map((modelKey) => { return this[modelKey as string].deleteMany(); }), ); diff --git a/src/infrastructure/logger/logger.service.spec.ts b/src/infrastructure/logger/logger.service.spec.ts index 1aa2a1ba..56ec1f8b 100644 --- a/src/infrastructure/logger/logger.service.spec.ts +++ b/src/infrastructure/logger/logger.service.spec.ts @@ -106,7 +106,7 @@ describe('LoggerService', () => { it('should respect environment-based log levels', () => { // Mock that we're in production - jest.spyOn(configService, 'get').mockImplementation(key => { + jest.spyOn(configService, 'get').mockImplementation((key) => { if (key === 'NODE_ENV') return 'production'; return null; diff --git a/src/infrastructure/logger/logger.service.ts b/src/infrastructure/logger/logger.service.ts index 7987fb91..5296c4c7 100644 --- a/src/infrastructure/logger/logger.service.ts +++ b/src/infrastructure/logger/logger.service.ts @@ -1,10 +1,4 @@ -import { - Inject, - Injectable, - LoggerService as NestLoggerService, - LogLevel, - Scope, -} from '@nestjs/common'; +import { Inject, Injectable, LoggerService as NestLoggerService, LogLevel, Scope } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; @Injectable({ scope: Scope.TRANSIENT }) @@ -70,11 +64,7 @@ export class LoggerService implements NestLoggerService { } if (level === 'error') { - console.error( - `${timestamp} ${level.toUpperCase()} ${contextStr}:`, - formattedMessage, - stack || '', - ); + console.error(`${timestamp} ${level.toUpperCase()} ${contextStr}:`, formattedMessage, stack || ''); } else if (level === 'warn') { console.warn(`${timestamp} ${level.toUpperCase()} ${contextStr}:`, formattedMessage); } else { diff --git a/src/infrastructure/repositories/base.repository.ts b/src/infrastructure/repositories/base.repository.ts index 8c53a15a..88a3f6b3 100644 --- a/src/infrastructure/repositories/base.repository.ts +++ b/src/infrastructure/repositories/base.repository.ts @@ -24,11 +24,7 @@ export abstract class BaseRepository { * @param returnValue - Optional fallback value to return * @returns The fallback value */ - protected handleError( - operation: string, - error: unknown, - returnValue: R | null = null, - ): R | null { + protected handleError(operation: string, error: unknown, returnValue: R | null = null): R | null { if (error instanceof Error) { this.logger.error( { diff --git a/src/infrastructure/repositories/file.repository.ts b/src/infrastructure/repositories/file.repository.ts index 69489fed..bc7be4e7 100644 --- a/src/infrastructure/repositories/file.repository.ts +++ b/src/infrastructure/repositories/file.repository.ts @@ -41,7 +41,7 @@ export class FileRepository extends BaseRepository implements IFileReposit orderBy: { createdAt: 'desc' }, }); - return files.map(file => this.mapToEntity(file)); + return files.map((file) => this.mapToEntity(file)); } async findAll(page: number = 1, limit: number = 20): Promise<{ files: File[]; total: number }> { @@ -57,7 +57,7 @@ export class FileRepository extends BaseRepository implements IFileReposit ]); return { - files: files.map(file => this.mapToEntity(file)), + files: files.map((file) => this.mapToEntity(file)), total, }; } diff --git a/src/infrastructure/repositories/password-reset.repository.ts b/src/infrastructure/repositories/password-reset.repository.ts index 56592cba..decf5c3b 100644 --- a/src/infrastructure/repositories/password-reset.repository.ts +++ b/src/infrastructure/repositories/password-reset.repository.ts @@ -9,10 +9,7 @@ import { Injectable } from '@nestjs/common'; import { BaseRepository } from './base.repository'; @Injectable() -export class PasswordResetRepository - extends BaseRepository - implements IPasswordResetRepository -{ +export class PasswordResetRepository extends BaseRepository implements IPasswordResetRepository { constructor(private readonly prisma: PrismaService) { super(); } diff --git a/src/infrastructure/repositories/permission.repository.ts b/src/infrastructure/repositories/permission.repository.ts index 6c7f0d70..0dc23d49 100644 --- a/src/infrastructure/repositories/permission.repository.ts +++ b/src/infrastructure/repositories/permission.repository.ts @@ -7,10 +7,7 @@ import { Injectable } from '@nestjs/common'; import { BaseRepository } from './base.repository'; @Injectable() -export class PermissionRepository - extends BaseRepository - implements IPermissionRepository -{ +export class PermissionRepository extends BaseRepository implements IPermissionRepository { constructor(private readonly prisma: PrismaService) { super(); } @@ -47,7 +44,7 @@ export class PermissionRepository return this.executeWithErrorHandling('findAll', async () => { const permissionRecords = await this.prisma.permission.findMany(); - return permissionRecords.map(record => this.mapToModel(record)); + return permissionRecords.map((record) => this.mapToModel(record)); }); } @@ -57,7 +54,7 @@ export class PermissionRepository where: { resource }, }); - return permissionRecords.map(record => this.mapToModel(record)); + return permissionRecords.map((record) => this.mapToModel(record)); }); } diff --git a/src/infrastructure/repositories/refresh-token.repository.ts b/src/infrastructure/repositories/refresh-token.repository.ts index 908cf5e8..067e9da7 100644 --- a/src/infrastructure/repositories/refresh-token.repository.ts +++ b/src/infrastructure/repositories/refresh-token.repository.ts @@ -9,10 +9,7 @@ import { ConfigService } from '@nestjs/config'; import { BaseRepository } from './base.repository'; @Injectable() -export class RefreshTokenRepository - extends BaseRepository - implements IRefreshTokenRepository -{ +export class RefreshTokenRepository extends BaseRepository implements IRefreshTokenRepository { constructor( private readonly prisma: PrismaService, private readonly configService: ConfigService, @@ -54,7 +51,7 @@ export class RefreshTokenRepository where: { userId }, }); - return tokenRecords.map(record => this.mapToModel(record)); + return tokenRecords.map((record) => this.mapToModel(record)); }); } @@ -123,10 +120,7 @@ export class RefreshTokenRepository } private mapToModel(record: PrismaRefreshToken): RefreshToken { - const refreshExpiration = parseInt( - this.configService.get('JWT_REFRESH_EXPIRATION', '7').replace('d', ''), - 10, - ); + const refreshExpiration = parseInt(this.configService.get('JWT_REFRESH_EXPIRATION', '7').replace('d', ''), 10); // Create value objects from primitive values const userIdVO = UserId.fromString(record.userId); diff --git a/src/infrastructure/repositories/role.repository.ts b/src/infrastructure/repositories/role.repository.ts index b72510c2..4e620a8d 100644 --- a/src/infrastructure/repositories/role.repository.ts +++ b/src/infrastructure/repositories/role.repository.ts @@ -73,7 +73,7 @@ export class RoleRepository extends BaseRepository implements IRoleReposit }, }); - return roleRecords.map(record => this.mapToModel(record as RoleWithPermissions)); + return roleRecords.map((record) => this.mapToModel(record as RoleWithPermissions)); } async findDefaultRole(): Promise { @@ -104,7 +104,7 @@ export class RoleRepository extends BaseRepository implements IRoleReposit isDefault: role.isDefault, permissions: { create: - role.permissions?.map(permission => ({ + role.permissions?.map((permission) => ({ permission: { connect: { id: permission.id.getValue() }, }, @@ -140,7 +140,7 @@ export class RoleRepository extends BaseRepository implements IRoleReposit isDefault: role.isDefault, permissions: { create: - role.permissions?.map(permission => ({ + role.permissions?.map((permission) => ({ permission: { connect: { id: permission.id.getValue() }, }, @@ -174,14 +174,11 @@ export class RoleRepository extends BaseRepository implements IRoleReposit private mapToModel(record: RoleWithPermissions): Role { // Map permissions first const permissions = - record.permissions?.map(permissionRelation => { + record.permissions?.map((permissionRelation) => { const permissionRecord = permissionRelation.permission; // Create ResourceAction value object - const resourceAction = new ResourceAction( - permissionRecord.resource, - permissionRecord.action as ActionType, - ); + const resourceAction = new ResourceAction(permissionRecord.resource, permissionRecord.action as ActionType); return Permission.fromData({ id: permissionRecord.id, diff --git a/src/infrastructure/repositories/user.repository.spec.ts b/src/infrastructure/repositories/user.repository.spec.ts index bf052a30..4e5a81f2 100644 --- a/src/infrastructure/repositories/user.repository.spec.ts +++ b/src/infrastructure/repositories/user.repository.spec.ts @@ -29,7 +29,7 @@ const mockPrismaService = { userRole: { deleteMany: jest.fn(), }, - $transaction: jest.fn(callback => callback(mockPrismaService)), + $transaction: jest.fn((callback) => callback(mockPrismaService)), }; describe('UserRepository', () => { @@ -159,10 +159,7 @@ describe('UserRepository', () => { new LastName('Doe'), ); - const mockUpdatedUser = createMockUserRecord( - existingUser.id.getValue(), - existingUser.email.getValue(), - ); + const mockUpdatedUser = createMockUserRecord(existingUser.id.getValue(), existingUser.email.getValue()); mockPrismaService.userRole.deleteMany.mockResolvedValue({ count: 0 }); mockPrismaService.user.update.mockResolvedValue(mockUpdatedUser); diff --git a/src/infrastructure/repositories/user.repository.ts b/src/infrastructure/repositories/user.repository.ts index bc7d9c57..c715a6a4 100644 --- a/src/infrastructure/repositories/user.repository.ts +++ b/src/infrastructure/repositories/user.repository.ts @@ -109,7 +109,7 @@ export class UserRepository extends BaseRepository implements IUserReposit }, }); - return userRecords.map(record => this.mapToModel(record as UserWithRelations)); + return userRecords.map((record) => this.mapToModel(record as UserWithRelations)); }); } @@ -158,7 +158,7 @@ export class UserRepository extends BaseRepository implements IUserReposit orderBy: { createdAt: 'desc' }, }); - const users = userRecords.map(record => this.mapToModel(record as UserWithRelations)); + const users = userRecords.map((record) => this.mapToModel(record as UserWithRelations)); return { users, total }; }); @@ -191,7 +191,7 @@ export class UserRepository extends BaseRepository implements IUserReposit }, }); - return userRecords.map(record => this.mapToModel(record as UserWithRelations)); + return userRecords.map((record) => this.mapToModel(record as UserWithRelations)); }); } @@ -209,7 +209,7 @@ export class UserRepository extends BaseRepository implements IUserReposit otpSecret: user.otpSecret, lastLoginAt: user.lastLoginAt, roles: { - create: user.roles.map(role => ({ + create: user.roles.map((role) => ({ role: { connect: { id: role.id.getValue() }, }, @@ -259,7 +259,7 @@ export class UserRepository extends BaseRepository implements IUserReposit otpSecret: user.otpSecret, lastLoginAt: user.lastLoginAt, roles: { - create: user.roles.map(role => ({ + create: user.roles.map((role) => ({ role: { connect: { id: role.id.getValue() }, }, @@ -303,19 +303,16 @@ export class UserRepository extends BaseRepository implements IUserReposit private mapToModel(record: UserWithRelations): User { // Map roles first - const roles = record.roles.map(roleRelation => { + const roles = record.roles.map((roleRelation) => { const roleRecord = roleRelation.role; // Map permissions const permissions = - roleRecord.permissions?.map(permissionRelation => { + roleRecord.permissions?.map((permissionRelation) => { const permissionRecord = permissionRelation.permission; // Create the ResourceAction value object - const resourceAction = new ResourceAction( - permissionRecord.resource, - permissionRecord.action as ActionType, - ); + const resourceAction = new ResourceAction(permissionRecord.resource, permissionRecord.action as ActionType); return Permission.fromData({ id: permissionRecord.id, diff --git a/src/infrastructure/services/throttler.service.spec.ts b/src/infrastructure/services/throttler.service.spec.ts index 791f458a..ed26bdbc 100644 --- a/src/infrastructure/services/throttler.service.spec.ts +++ b/src/infrastructure/services/throttler.service.spec.ts @@ -2,10 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { ConfigService } from '@nestjs/config'; import { ThrottlerService } from './throttler.service'; import { ThrottleLimit } from '@core/value-objects/throttle-limit.vo'; -import { - InvalidThrottleIdentifierException, - ThrottlingException, -} from '@core/exceptions/domain-exceptions'; +import { InvalidThrottleIdentifierException, ThrottlingException } from '@core/exceptions/domain-exceptions'; describe('ThrottlerService', () => { let service: ThrottlerService; @@ -74,9 +71,7 @@ describe('ThrottlerService', () => { await service.trackRequest('test-id', throttleLimit); // Third request should throw - await expect(service.trackRequest('test-id', throttleLimit)).rejects.toThrow( - ThrottlingException, - ); + await expect(service.trackRequest('test-id', throttleLimit)).rejects.toThrow(ThrottlingException); try { await service.trackRequest('test-id', throttleLimit); @@ -88,12 +83,8 @@ describe('ThrottlerService', () => { describe('getRemainingRequests', () => { it('should throw an exception when identifier is empty', async () => { - await expect(service.getRemainingRequests('')).rejects.toThrow( - InvalidThrottleIdentifierException, - ); - await expect(service.getRemainingRequests(null)).rejects.toThrow( - InvalidThrottleIdentifierException, - ); + await expect(service.getRemainingRequests('')).rejects.toThrow(InvalidThrottleIdentifierException); + await expect(service.getRemainingRequests(null)).rejects.toThrow(InvalidThrottleIdentifierException); }); it('should return the full limit for first-time requests', async () => { @@ -117,9 +108,7 @@ describe('ThrottlerService', () => { describe('resetThrottling', () => { it('should throw an exception when identifier is empty', async () => { await expect(service.resetThrottling('')).rejects.toThrow(InvalidThrottleIdentifierException); - await expect(service.resetThrottling(null)).rejects.toThrow( - InvalidThrottleIdentifierException, - ); + await expect(service.resetThrottling(null)).rejects.toThrow(InvalidThrottleIdentifierException); }); it('should reset throttling for an identifier', async () => { diff --git a/src/infrastructure/services/throttler.service.ts b/src/infrastructure/services/throttler.service.ts index 1d2a5111..db3c3410 100644 --- a/src/infrastructure/services/throttler.service.ts +++ b/src/infrastructure/services/throttler.service.ts @@ -2,10 +2,7 @@ import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { IThrottlerService } from '@core/services/throttler.service'; import { ThrottleLimit } from '@core/value-objects/throttle-limit.vo'; -import { - InvalidThrottleIdentifierException, - ThrottlingException, -} from '@core/exceptions/domain-exceptions'; +import { InvalidThrottleIdentifierException, ThrottlingException } from '@core/exceptions/domain-exceptions'; @Injectable() export class ThrottlerService implements IThrottlerService { @@ -13,10 +10,7 @@ export class ThrottlerService implements IThrottlerService { constructor(private readonly configService: ConfigService) {} - async isAllowed( - identifier: string, - throttleLimit: ThrottleLimit = this.getDefaultThrottleLimit(), - ): Promise { + async isAllowed(identifier: string, throttleLimit: ThrottleLimit = this.getDefaultThrottleLimit()): Promise { if (!identifier) { throw new InvalidThrottleIdentifierException(); } @@ -38,10 +32,7 @@ export class ThrottlerService implements IThrottlerService { return record.count < throttleLimit.getLimit; } - async trackRequest( - identifier: string, - throttleLimit: ThrottleLimit = this.getDefaultThrottleLimit(), - ): Promise { + async trackRequest(identifier: string, throttleLimit: ThrottleLimit = this.getDefaultThrottleLimit()): Promise { if (!identifier) { throw new InvalidThrottleIdentifierException(); } diff --git a/src/infrastructure/storage/providers/minio.provider.ts b/src/infrastructure/storage/providers/minio.provider.ts index 9d5ab6a0..859fdddd 100644 --- a/src/infrastructure/storage/providers/minio.provider.ts +++ b/src/infrastructure/storage/providers/minio.provider.ts @@ -31,7 +31,7 @@ export class MinioStorageProvider implements IStorageProvider { region: minioConfig.region, }); - this.initializeBuckets().catch(err => { + this.initializeBuckets().catch((err) => { console.error('Error initializing MinIO buckets:', err); }); } diff --git a/src/infrastructure/storage/providers/s3.provider.ts b/src/infrastructure/storage/providers/s3.provider.ts index f1d1e158..e1d49dcd 100644 --- a/src/infrastructure/storage/providers/s3.provider.ts +++ b/src/infrastructure/storage/providers/s3.provider.ts @@ -1,11 +1,6 @@ import { Injectable, Inject } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; -import { - S3Client, - PutObjectCommand, - DeleteObjectCommand, - GetObjectCommand, -} from '@aws-sdk/client-s3'; +import { S3Client, PutObjectCommand, DeleteObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3'; import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; import { v4 as uuidv4 } from 'uuid'; import * as path from 'path'; diff --git a/src/main.ts b/src/main.ts index 7fb81617..c7e2337d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -43,9 +43,7 @@ async function bootstrap() { app.useGlobalFilters(new AllExceptionsFilter(exceptionLogger)); // Enable CORS with security settings - const allowedOrigins = configService.get('ALLOWED_ORIGINS')?.split(',') || [ - 'http://localhost:3000', - ]; + const allowedOrigins = configService.get('ALLOWED_ORIGINS')?.split(',') || ['http://localhost:3000']; app.enableCors({ origin: allowedOrigins, credentials: true, @@ -101,10 +99,7 @@ async function bootstrap() { basicAuth({ challenge: true, users: { - [configService.get('SWAGGER_USER', 'admin')]: configService.get( - 'SWAGGER_PASSWORD', - 'admin', - ), + [configService.get('SWAGGER_USER', 'admin')]: configService.get('SWAGGER_PASSWORD', 'admin'), }, }), ); @@ -144,7 +139,7 @@ async function bootstrap() { }); } -bootstrap().catch(err => { +bootstrap().catch((err) => { console.error('Error starting application:', err); process.exit(1); }); diff --git a/src/presentation/filters/all-exceptions.filter.ts b/src/presentation/filters/all-exceptions.filter.ts index 15bb23cc..da340c31 100644 --- a/src/presentation/filters/all-exceptions.filter.ts +++ b/src/presentation/filters/all-exceptions.filter.ts @@ -1,11 +1,4 @@ -import { - ExceptionFilter, - Catch, - ArgumentsHost, - HttpException, - HttpStatus, - Inject, -} from '@nestjs/common'; +import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus, Inject } from '@nestjs/common'; import { Request, Response } from 'express'; import { LoggerService } from '@infrastructure/logger/logger.service'; diff --git a/src/presentation/guards/throttler.guard.ts b/src/presentation/guards/throttler.guard.ts index 2aa66046..10b1356f 100644 --- a/src/presentation/guards/throttler.guard.ts +++ b/src/presentation/guards/throttler.guard.ts @@ -46,7 +46,7 @@ export class ThrottlerGuard implements CanActivate { const ignoreUserAgents = this.configService.get('throttler.ignoreUserAgents'); const userAgent = request.headers['user-agent'] || ''; - if (ignoreUserAgents && ignoreUserAgents.some(agent => userAgent.includes(agent))) { + if (ignoreUserAgents && ignoreUserAgents.some((agent) => userAgent.includes(agent))) { return true; } @@ -63,10 +63,7 @@ export class ThrottlerGuard implements CanActivate { const response = context.switchToHttp().getResponse(); response.header('X-RateLimit-Limit', throttleLimit.getLimit.toString()); response.header('X-RateLimit-Remaining', remaining.toString()); - response.header( - 'X-RateLimit-Reset', - Math.ceil(Date.now() / 1000 + throttleLimit.getTtl).toString(), - ); + response.header('X-RateLimit-Reset', Math.ceil(Date.now() / 1000 + throttleLimit.getTtl).toString()); return true; } diff --git a/src/presentation/interceptors/logging.interceptor.ts b/src/presentation/interceptors/logging.interceptor.ts index d6588365..481869a1 100644 --- a/src/presentation/interceptors/logging.interceptor.ts +++ b/src/presentation/interceptors/logging.interceptor.ts @@ -26,7 +26,7 @@ export class LoggingInterceptor implements NestInterceptor { const now = Date.now(); return next.handle().pipe( - tap(data => { + tap((data) => { // Log the response this.logger.log({ message: `Request completed`, diff --git a/src/presentation/interceptors/transform.interceptor.ts b/src/presentation/interceptors/transform.interceptor.ts index 838be179..fbe3f333 100644 --- a/src/presentation/interceptors/transform.interceptor.ts +++ b/src/presentation/interceptors/transform.interceptor.ts @@ -20,7 +20,7 @@ export class TransformInterceptor implements NestInterceptor> const acceptLanguage = request.headers['accept-language']; return next.handle().pipe( - map(data => { + map((data) => { // Handle responses with message field let responseData = data; let message: string | undefined; diff --git a/src/presentation/modules/admin/admin-health.controller.ts b/src/presentation/modules/admin/admin-health.controller.ts index 89dac93a..57ec8d4a 100644 --- a/src/presentation/modules/admin/admin-health.controller.ts +++ b/src/presentation/modules/admin/admin-health.controller.ts @@ -13,12 +13,7 @@ import { GetReadinessQuery } from '@application/queries/health/get-readiness.que import { GetLivenessQuery } from '@application/queries/health/get-liveness.query'; // Response interfaces -import { - HealthCheckResponse, - DatabaseHealthResponse, - ReadinessResponse, - LivenessResponse, -} from '@application/dtos'; +import { HealthCheckResponse, DatabaseHealthResponse, ReadinessResponse, LivenessResponse } from '@application/dtos'; @ApiTags('admin-health') @Controller('admin/health') diff --git a/src/presentation/modules/admin/admin-role.controller.ts b/src/presentation/modules/admin/admin-role.controller.ts index ef4b174f..235a1b4b 100644 --- a/src/presentation/modules/admin/admin-role.controller.ts +++ b/src/presentation/modules/admin/admin-role.controller.ts @@ -1,15 +1,4 @@ -import { - Controller, - Get, - Post, - Put, - Delete, - Param, - Body, - HttpCode, - HttpStatus, - UseGuards, -} from '@nestjs/common'; +import { Controller, Get, Post, Put, Delete, Param, Body, HttpCode, HttpStatus, UseGuards } from '@nestjs/common'; import { QueryBus, CommandBus } from '@nestjs/cqrs'; import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiParam } from '@nestjs/swagger'; @@ -101,12 +90,7 @@ export class AdminRoleController { @ApiResponse({ status: HttpStatus.FORBIDDEN, description: 'Admin access required' }) async updateRole(@Param('id') id: string, @Body() updateRoleDto: UpdateRoleRequest) { return this.commandBus.execute( - new UpdateRoleCommand( - id, - updateRoleDto.name, - updateRoleDto.description, - updateRoleDto.isDefault, - ), + new UpdateRoleCommand(id, updateRoleDto.name, updateRoleDto.description, updateRoleDto.isDefault), ); } @@ -139,10 +123,7 @@ export class AdminRoleController { @ApiResponse({ status: HttpStatus.OK, description: 'Permission assigned to role successfully' }) @ApiResponse({ status: HttpStatus.NOT_FOUND, description: 'Role or permission not found' }) @ApiResponse({ status: HttpStatus.FORBIDDEN, description: 'Admin access required' }) - async assignPermissionToRole( - @Param('roleId') roleId: string, - @Param('permissionId') permissionId: string, - ) { + async assignPermissionToRole(@Param('roleId') roleId: string, @Param('permissionId') permissionId: string) { return this.commandBus.execute(new AssignPermissionCommand(roleId, permissionId)); } @@ -162,10 +143,7 @@ export class AdminRoleController { @ApiResponse({ status: HttpStatus.OK, description: 'Permission removed from role successfully' }) @ApiResponse({ status: HttpStatus.NOT_FOUND, description: 'Role or permission not found' }) @ApiResponse({ status: HttpStatus.FORBIDDEN, description: 'Admin access required' }) - async removePermissionFromRole( - @Param('roleId') roleId: string, - @Param('permissionId') permissionId: string, - ) { + async removePermissionFromRole(@Param('roleId') roleId: string, @Param('permissionId') permissionId: string) { return this.commandBus.execute(new RemovePermissionCommand(roleId, permissionId)); } } diff --git a/src/presentation/modules/admin/admin-user.controller.ts b/src/presentation/modules/admin/admin-user.controller.ts index 5821cf43..4bff7e8d 100644 --- a/src/presentation/modules/admin/admin-user.controller.ts +++ b/src/presentation/modules/admin/admin-user.controller.ts @@ -13,14 +13,7 @@ import { Query, } from '@nestjs/common'; import { QueryBus, CommandBus } from '@nestjs/cqrs'; -import { - ApiTags, - ApiOperation, - ApiResponse, - ApiBearerAuth, - ApiParam, - ApiQuery, -} from '@nestjs/swagger'; +import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiParam, ApiQuery } from '@nestjs/swagger'; // Guards & Decorators import { PermissionsGuard } from '@presentation/guards/permissions.guard'; @@ -76,11 +69,7 @@ export class AdminUserController { }) @ApiResponse({ status: HttpStatus.OK, description: 'Returns a paginated list of users' }) @ApiResponse({ status: HttpStatus.FORBIDDEN, description: 'Admin access required' }) - async getAllUsers( - @Query('search') search?: string, - @Query('page') page?: number, - @Query('limit') limit?: number, - ) { + async getAllUsers(@Query('search') search?: string, @Query('page') page?: number, @Query('limit') limit?: number) { return this.queryBus.execute(new GetUsersQuery(search, page, limit)); } @@ -105,12 +94,7 @@ export class AdminUserController { @ApiResponse({ status: HttpStatus.FORBIDDEN, description: 'Admin access required' }) async updateUser(@Param('id') id: string, @Body() updateUserDto: UpdateUserRequest) { await this.commandBus.execute( - new AdminUpdateUserCommand( - id, - updateUserDto.firstName, - updateUserDto.lastName, - updateUserDto.email, - ), + new AdminUpdateUserCommand(id, updateUserDto.firstName, updateUserDto.lastName, updateUserDto.email), ); return { message: 'User updated successfully' }; @@ -136,13 +120,8 @@ export class AdminUserController { @ApiResponse({ status: HttpStatus.BAD_REQUEST, description: 'Invalid input data' }) @ApiResponse({ status: HttpStatus.NOT_FOUND, description: 'User not found' }) @ApiResponse({ status: HttpStatus.FORBIDDEN, description: 'Admin access required' }) - async changeUserPassword( - @Param('id') id: string, - @Body() changePasswordDto: AdminChangePasswordRequest, - ) { - await this.commandBus.execute( - new AdminChangePasswordCommand(id, changePasswordDto.newPassword), - ); + async changeUserPassword(@Param('id') id: string, @Body() changePasswordDto: AdminChangePasswordRequest) { + await this.commandBus.execute(new AdminChangePasswordCommand(id, changePasswordDto.newPassword)); return { message: 'Password changed successfully' }; } @@ -172,9 +151,7 @@ export class AdminUserController { @Body() assignRoleDto: AssignRoleRequest, @CurrentUser() currentUser: IJwtPayload, ) { - return this.commandBus.execute( - new AssignRoleCommand(id, assignRoleDto.roleId, currentUser.sub), - ); + return this.commandBus.execute(new AssignRoleCommand(id, assignRoleDto.roleId, currentUser.sub)); } @Delete(':id/roles/:roleId') diff --git a/src/presentation/modules/admin/admin.module.ts b/src/presentation/modules/admin/admin.module.ts index c5bf82a7..4b8ce85d 100644 --- a/src/presentation/modules/admin/admin.module.ts +++ b/src/presentation/modules/admin/admin.module.ts @@ -120,13 +120,7 @@ const commandHandlers = [ }), }), ], - controllers: [ - AdminAuthController, - AdminController, - AdminUserController, - AdminRoleController, - AdminHealthController, - ], + controllers: [AdminAuthController, AdminController, AdminUserController, AdminRoleController, AdminHealthController], providers: [ // Services { diff --git a/src/presentation/modules/auth/auth.controller.ts b/src/presentation/modules/auth/auth.controller.ts index 7ba65491..9b908722 100644 --- a/src/presentation/modules/auth/auth.controller.ts +++ b/src/presentation/modules/auth/auth.controller.ts @@ -138,8 +138,7 @@ export class AuthController { @HttpCode(HttpStatus.OK) @ApiOperation({ summary: 'Verify email with verification code', - description: - 'Verify email with the code received. If successful, returns auth tokens like the login endpoint.', + description: 'Verify email with the code received. If successful, returns auth tokens like the login endpoint.', }) @ApiResponse({ status: HttpStatus.OK, @@ -163,9 +162,7 @@ export class AuthController { description: 'Returns the verification status of the email', }) async checkEmailVerificationStatus(@Param('email') email: string) { - const isVerified = await this.commandBus.execute( - new CheckEmailVerificationStatusCommand(email), - ); + const isVerified = await this.commandBus.execute(new CheckEmailVerificationStatusCommand(email)); return { verified: isVerified }; } diff --git a/src/presentation/modules/auth/providers/email.provider.ts b/src/presentation/modules/auth/providers/email.provider.ts index 0d8363a4..16bc78ec 100644 --- a/src/presentation/modules/auth/providers/email.provider.ts +++ b/src/presentation/modules/auth/providers/email.provider.ts @@ -111,10 +111,7 @@ export class EmailProvider implements OnModuleInit { * @param resetToken The password reset token * @returns Promise with the result of the operation */ - async sendPasswordResetEmail( - email: string, - resetToken: string, - ): Promise { + async sendPasswordResetEmail(email: string, resetToken: string): Promise { const transporter = await this.getTransporter(); const appName = this.configService.get('APP_NAME', 'Our Application'); const frontendUrl = this.configService.get('FRONTEND_URL', 'https://example.com'); diff --git a/src/presentation/modules/auth/providers/token.provider.ts b/src/presentation/modules/auth/providers/token.provider.ts index 8b6dcaf7..ce46529d 100644 --- a/src/presentation/modules/auth/providers/token.provider.ts +++ b/src/presentation/modules/auth/providers/token.provider.ts @@ -21,7 +21,7 @@ export class TokenProvider { sub: user.id.getValue(), email: user.email.getValue(), emailVerified: isEmailVerified, - roles: user.roles.map(role => role.name), + roles: user.roles.map((role) => role.name), permissions: permissions, }; } diff --git a/src/presentation/modules/storage/storage.controller.ts b/src/presentation/modules/storage/storage.controller.ts index 66e37a92..4fb9d302 100644 --- a/src/presentation/modules/storage/storage.controller.ts +++ b/src/presentation/modules/storage/storage.controller.ts @@ -16,15 +16,7 @@ import { } from '@nestjs/common'; import { FileInterceptor } from '@nestjs/platform-express'; import { CommandBus, QueryBus } from '@nestjs/cqrs'; -import { - ApiTags, - ApiConsumes, - ApiBody, - ApiOperation, - ApiParam, - ApiBearerAuth, - ApiResponse, -} from '@nestjs/swagger'; +import { ApiTags, ApiConsumes, ApiBody, ApiOperation, ApiParam, ApiBearerAuth, ApiResponse } from '@nestjs/swagger'; import { PermissionsGuard } from '@presentation/guards/permissions.guard'; import { RequiresResourceAction } from '@shared/decorators/resource-action.decorator'; @@ -136,8 +128,6 @@ export class StorageController { @Body() updateFileAccessDto: UpdateFileAccessRequest, @CurrentUser() user: IJwtPayload, ): Promise { - return this.commandBus.execute( - new UpdateFileAccessCommand(id, updateFileAccessDto.isPublic, user.sub), - ); + return this.commandBus.execute(new UpdateFileAccessCommand(id, updateFileAccessDto.isPublic, user.sub)); } } diff --git a/src/presentation/modules/storage/storage.module.ts b/src/presentation/modules/storage/storage.module.ts index 448670ec..41b402be 100644 --- a/src/presentation/modules/storage/storage.module.ts +++ b/src/presentation/modules/storage/storage.module.ts @@ -17,11 +17,7 @@ import { GetAllFilesQueryHandler } from '@application/queries/storage/get-all-fi // Mappers import { FileMapper } from '@application/mappers/file.mapper'; -const CommandHandlers = [ - UploadFileCommandHandler, - DeleteFileCommandHandler, - UpdateFileAccessCommandHandler, -]; +const CommandHandlers = [UploadFileCommandHandler, DeleteFileCommandHandler, UpdateFileAccessCommandHandler]; const QueryHandlers = [GetFileQueryHandler, GetUserFilesQueryHandler, GetAllFilesQueryHandler]; diff --git a/src/presentation/modules/user/user.controller.ts b/src/presentation/modules/user/user.controller.ts index 962207f8..3a24e709 100644 --- a/src/presentation/modules/user/user.controller.ts +++ b/src/presentation/modules/user/user.controller.ts @@ -40,17 +40,9 @@ export class UserController { @ApiOperation({ summary: 'Update current user profile' }) @ApiResponse({ status: HttpStatus.OK, description: 'Profile updated successfully' }) @ApiResponse({ status: HttpStatus.BAD_REQUEST, description: 'Invalid input data' }) - async updateCurrentUserProfile( - @CurrentUser() user: IJwtPayload, - @Body() updateUserDto: UpdateUserRequest, - ) { + async updateCurrentUserProfile(@CurrentUser() user: IJwtPayload, @Body() updateUserDto: UpdateUserRequest) { return this.commandBus.execute( - new UpdateUserCommand( - user.sub, - updateUserDto.firstName, - updateUserDto.lastName, - updateUserDto.email, - ), + new UpdateUserCommand(user.sub, updateUserDto.firstName, updateUserDto.lastName, updateUserDto.email), ); } @@ -60,16 +52,9 @@ export class UserController { @ApiResponse({ status: HttpStatus.OK, description: 'Password changed successfully' }) @ApiResponse({ status: HttpStatus.BAD_REQUEST, description: 'Invalid input data' }) @ApiResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Current password is incorrect' }) - async changeCurrentUserPassword( - @CurrentUser() user: IJwtPayload, - @Body() changePasswordDto: ChangePasswordRequest, - ) { + async changeCurrentUserPassword(@CurrentUser() user: IJwtPayload, @Body() changePasswordDto: ChangePasswordRequest) { await this.commandBus.execute( - new ChangePasswordCommand( - user.sub, - changePasswordDto.newPassword, - changePasswordDto.currentPassword, - ), + new ChangePasswordCommand(user.sub, changePasswordDto.newPassword, changePasswordDto.currentPassword), ); return { message: 'Password changed successfully' }; @@ -80,10 +65,7 @@ export class UserController { @ApiOperation({ summary: 'Verify current user password' }) @ApiResponse({ status: HttpStatus.OK, description: 'Password verification result' }) @ApiResponse({ status: HttpStatus.BAD_REQUEST, description: 'Invalid input data' }) - async verifyCurrentUserPassword( - @CurrentUser() user: IJwtPayload, - @Body('password') password: string, - ) { + async verifyCurrentUserPassword(@CurrentUser() user: IJwtPayload, @Body('password') password: string) { const isValid = await this.commandBus.execute(new VerifyPasswordCommand(user.sub, password)); return { valid: isValid }; diff --git a/src/presentation/modules/user/user.module.ts b/src/presentation/modules/user/user.module.ts index 874fb3d8..79571602 100644 --- a/src/presentation/modules/user/user.module.ts +++ b/src/presentation/modules/user/user.module.ts @@ -24,11 +24,7 @@ import { VerifyPasswordCommandHandler } from '@application/commands/user/verify- const queryHandlers = [GetUserQueryHandler]; -const commandHandlers = [ - UpdateUserCommandHandler, - ChangePasswordCommandHandler, - VerifyPasswordCommandHandler, -]; +const commandHandlers = [UpdateUserCommandHandler, ChangePasswordCommandHandler, VerifyPasswordCommandHandler]; @Module({ imports: [CqrsModule, PrismaModule, CoreModule], diff --git a/src/test/mocks/repositories.factory.ts b/src/test/mocks/repositories.factory.ts index b46f1bff..2f1e6fc4 100644 --- a/src/test/mocks/repositories.factory.ts +++ b/src/test/mocks/repositories.factory.ts @@ -19,7 +19,7 @@ export const createMockUserRepository = () => ({ ...mockUserRepository, findById: jest.fn().mockImplementation(mockUserRepository.findById), findByEmail: jest.fn().mockImplementation(mockUserRepository.findByEmail), - create: jest.fn().mockImplementation(userData => { + create: jest.fn().mockImplementation((userData) => { return Promise.resolve({ id: '550e8400-e29b-41d4-a716-446655440099', ...userData, @@ -40,7 +40,7 @@ export const createMockUserRepository = () => ({ deactivate: jest.fn(), }); }), - update: jest.fn().mockImplementation(user => { + update: jest.fn().mockImplementation((user) => { return Promise.resolve({ ...user, email: user.email || { getValue: () => 'updated@example.com' }, diff --git a/src/test/mocks/repositories.mock.ts b/src/test/mocks/repositories.mock.ts index 9847bbfc..03a506da 100644 --- a/src/test/mocks/repositories.mock.ts +++ b/src/test/mocks/repositories.mock.ts @@ -6,7 +6,7 @@ import { userFixture, adminFixture } from '../fixtures/user.fixtures'; // Mock User Repository export const mockUserRepository = { - findById: jest.fn().mockImplementation(id => { + findById: jest.fn().mockImplementation((id) => { if (id === userFixture.id) { return Promise.resolve(userFixture); } else if (id === adminFixture.id) { @@ -16,7 +16,7 @@ export const mockUserRepository = { return Promise.resolve(null); }), - findByEmail: jest.fn().mockImplementation(email => { + findByEmail: jest.fn().mockImplementation((email) => { if (email === userFixture.email) { return Promise.resolve(userFixture); } else if (email === adminFixture.email) { @@ -26,7 +26,7 @@ export const mockUserRepository = { return Promise.resolve(null); }), - create: jest.fn().mockImplementation(userData => { + create: jest.fn().mockImplementation((userData) => { return Promise.resolve({ id: '550e8400-e29b-41d4-a716-446655440099', ...userData, @@ -48,7 +48,7 @@ export const mockUserRepository = { // Mock Role Repository export const mockRoleRepository = { - findById: jest.fn().mockImplementation(id => { + findById: jest.fn().mockImplementation((id) => { if (id === '1') { return Promise.resolve({ id: '1', @@ -74,7 +74,7 @@ export const mockRoleRepository = { return Promise.resolve(null); }), - findByName: jest.fn().mockImplementation(name => { + findByName: jest.fn().mockImplementation((name) => { if (name === 'admin') { return Promise.resolve({ id: '1', @@ -129,7 +129,7 @@ export const mockRoleRepository = { }, ]), - create: jest.fn().mockImplementation(roleData => { + create: jest.fn().mockImplementation((roleData) => { return Promise.resolve({ id: '3', ...roleData, @@ -170,7 +170,7 @@ export const mockRoleRepository = { // Mock Refresh Token Repository export const mockRefreshTokenRepository = { - findByToken: jest.fn().mockImplementation(token => { + findByToken: jest.fn().mockImplementation((token) => { if (token === '550e8400-e29b-41d4-a716-446655440000') { return Promise.resolve({ id: '1', @@ -184,7 +184,7 @@ export const mockRefreshTokenRepository = { return Promise.resolve(null); }), - create: jest.fn().mockImplementation(tokenData => { + create: jest.fn().mockImplementation((tokenData) => { return Promise.resolve({ id: '1', ...tokenData, @@ -199,7 +199,7 @@ export const mockRefreshTokenRepository = { // Mock Permission Repository export const mockPermissionRepository = { - findById: jest.fn().mockImplementation(id => { + findById: jest.fn().mockImplementation((id) => { const permissions = { '1': { id: '1', name: 'user:read' }, '2': { id: '2', name: 'user:write' }, @@ -212,7 +212,7 @@ export const mockPermissionRepository = { return Promise.resolve(permissions[id] || null); }), - findByName: jest.fn().mockImplementation(name => { + findByName: jest.fn().mockImplementation((name) => { const permissionMapping = { 'user:read': { id: '1', name: 'user:read' }, 'user:write': { id: '2', name: 'user:write' }, @@ -237,7 +237,7 @@ export const mockPermissionRepository = { // Mock Email Verification Repository export const mockEmailVerificationRepository = { - findByEmail: jest.fn().mockImplementation(email => { + findByEmail: jest.fn().mockImplementation((email) => { if (email === 'test@example.com') { return Promise.resolve({ id: '1', @@ -251,7 +251,7 @@ export const mockEmailVerificationRepository = { return Promise.resolve(null); }), - create: jest.fn().mockImplementation(data => { + create: jest.fn().mockImplementation((data) => { return Promise.resolve({ id: '1', ...data, @@ -273,7 +273,7 @@ export const mockEmailVerificationRepository = { // Mock OTP Repository export const mockOtpRepository = { - findByUserId: jest.fn().mockImplementation(userId => { + findByUserId: jest.fn().mockImplementation((userId) => { if (userId === userFixture.id) { return Promise.resolve({ id: '1', @@ -286,7 +286,7 @@ export const mockOtpRepository = { return Promise.resolve(null); }), - create: jest.fn().mockImplementation(data => { + create: jest.fn().mockImplementation((data) => { return Promise.resolve({ id: '1', ...data, @@ -308,7 +308,7 @@ export const mockOtpRepository = { // Mock Password Reset Repository export const mockPasswordResetRepository = { - findByToken: jest.fn().mockImplementation(token => { + findByToken: jest.fn().mockImplementation((token) => { if (token === 'reset-token-123') { return Promise.resolve({ id: '1', @@ -322,7 +322,7 @@ export const mockPasswordResetRepository = { return Promise.resolve(null); }), - create: jest.fn().mockImplementation(data => { + create: jest.fn().mockImplementation((data) => { return Promise.resolve({ id: '1', ...data, diff --git a/test/auth.e2e-spec.ts b/test/auth.e2e-spec.ts index 86871d5f..176cd4b1 100644 --- a/test/auth.e2e-spec.ts +++ b/test/auth.e2e-spec.ts @@ -1,9 +1,9 @@ -import { Test, TestingModule } from '@nestjs/testing'; +import { ThrottlerService } from '@infrastructure/services/throttler.service'; import { INestApplication, ValidationPipe } from '@nestjs/common'; +import { JwtService } from '@nestjs/jwt'; +import { Test, TestingModule } from '@nestjs/testing'; import * as request from 'supertest'; import { AppModule } from '../src/app.module'; -import { JwtService } from '@nestjs/jwt'; -import { ThrottlerService } from '@infrastructure/services/throttler.service'; describe('AuthController (e2e)', () => { let app: INestApplication; @@ -61,7 +61,7 @@ describe('AuthController (e2e)', () => { .get('/api/auth/me') .set('Authorization', `Bearer ${accessToken}`) .expect(200) - .expect(res => { + .expect((res) => { expect(res.body).toHaveProperty('id'); expect(res.body).toHaveProperty('email'); expect(res.body).toHaveProperty('roles'); @@ -69,10 +69,7 @@ describe('AuthController (e2e)', () => { }); it('should fail with invalid token', () => { - return request(app.getHttpServer()) - .get('/api/auth/me') - .set('Authorization', 'Bearer invalid-token') - .expect(401); + return request(app.getHttpServer()).get('/api/auth/me').set('Authorization', 'Bearer invalid-token').expect(401); }); it('should fail without token', () => { @@ -103,7 +100,7 @@ describe('AuthController (e2e)', () => { lastName: 'User', }) .expect(201) - .expect(res => { + .expect((res) => { expect(res.body).toHaveProperty('id'); expect(res.body).toHaveProperty('email', 'test-user@example.com'); }); @@ -129,7 +126,7 @@ describe('AuthController (e2e)', () => { password: 'Password123!', }) .expect(200) - .expect(res => { + .expect((res) => { expect(res.body).toHaveProperty('accessToken'); expect(res.body).toHaveProperty('refreshToken'); expect(res.body).toHaveProperty('user'); diff --git a/test/role.e2e-spec.ts b/test/role.e2e-spec.ts index 989d3a80..a71d381d 100644 --- a/test/role.e2e-spec.ts +++ b/test/role.e2e-spec.ts @@ -74,9 +74,7 @@ describe('RoleController (e2e)', () => { describe('GET /roles/:id', () => { it('should deny access when not authenticated', () => { - return request(app.getHttpServer()) - .get('/api/roles/550e8400-e29b-41d4-a716-446655440000') - .expect(401); + return request(app.getHttpServer()).get('/api/roles/550e8400-e29b-41d4-a716-446655440000').expect(401); }); it.skip('should get role by ID when authenticated with permissions', () => { @@ -158,26 +156,20 @@ describe('RoleController (e2e)', () => { describe('DELETE /roles/:id', () => { it('should deny access when not authenticated', () => { - return request(app.getHttpServer()) - .delete('/api/roles/550e8400-e29b-41d4-a716-446655440000') - .expect(401); + return request(app.getHttpServer()).delete('/api/roles/550e8400-e29b-41d4-a716-446655440000').expect(401); }); }); describe('Role permissions endpoints', () => { it('should deny access when not authenticated for assigning permissions', () => { return request(app.getHttpServer()) - .post( - '/api/roles/550e8400-e29b-41d4-a716-446655440000/permissions/550e8400-e29b-41d4-a716-446655440001', - ) + .post('/api/roles/550e8400-e29b-41d4-a716-446655440000/permissions/550e8400-e29b-41d4-a716-446655440001') .expect(401); }); it('should deny access when not authenticated for removing permissions', () => { return request(app.getHttpServer()) - .delete( - '/api/roles/550e8400-e29b-41d4-a716-446655440000/permissions/550e8400-e29b-41d4-a716-446655440001', - ) + .delete('/api/roles/550e8400-e29b-41d4-a716-446655440000/permissions/550e8400-e29b-41d4-a716-446655440001') .expect(401); }); }); diff --git a/test/user.e2e-spec.ts b/test/user.e2e-spec.ts index 75487d02..f7c52e94 100644 --- a/test/user.e2e-spec.ts +++ b/test/user.e2e-spec.ts @@ -82,9 +82,7 @@ describe('UserController (e2e)', () => { describe('GET /users/:id', () => { it('should require authentication', () => { - return request(app.getHttpServer()) - .get('/api/users/550e8400-e29b-41d4-a716-446655440000') - .expect(401); + return request(app.getHttpServer()).get('/api/users/550e8400-e29b-41d4-a716-446655440000').expect(401); }); it('should deny access to regular users', () => {