86
package-lock.json
generated
@@ -20,6 +20,7 @@
|
|||||||
"@nestjs/passport": "^11.0.5",
|
"@nestjs/passport": "^11.0.5",
|
||||||
"@nestjs/platform-express": "^11.0.1",
|
"@nestjs/platform-express": "^11.0.1",
|
||||||
"@nestjs/platform-socket.io": "^11.1.11",
|
"@nestjs/platform-socket.io": "^11.1.11",
|
||||||
|
"@nestjs/serve-static": "^5.0.4",
|
||||||
"@nestjs/swagger": "^11.2.4",
|
"@nestjs/swagger": "^11.2.4",
|
||||||
"@nestjs/terminus": "^11.0.0",
|
"@nestjs/terminus": "^11.0.0",
|
||||||
"@nestjs/throttler": "^6.5.0",
|
"@nestjs/throttler": "^6.5.0",
|
||||||
@@ -53,6 +54,7 @@
|
|||||||
"@types/bcrypt": "^6.0.0",
|
"@types/bcrypt": "^6.0.0",
|
||||||
"@types/express": "^5.0.0",
|
"@types/express": "^5.0.0",
|
||||||
"@types/jest": "^30.0.0",
|
"@types/jest": "^30.0.0",
|
||||||
|
"@types/multer": "^2.1.0",
|
||||||
"@types/node": "^22.10.7",
|
"@types/node": "^22.10.7",
|
||||||
"@types/nodemailer": "^7.0.4",
|
"@types/nodemailer": "^7.0.4",
|
||||||
"@types/passport-jwt": "^4.0.1",
|
"@types/passport-jwt": "^4.0.1",
|
||||||
@@ -1137,7 +1139,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
|
||||||
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.27.1",
|
"@babel/code-frame": "^7.27.1",
|
||||||
"@babel/generator": "^7.28.5",
|
"@babel/generator": "^7.28.5",
|
||||||
@@ -3075,7 +3076,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
|
||||||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-deep-equal": "^3.1.3",
|
"fast-deep-equal": "^3.1.3",
|
||||||
"fast-uri": "^3.0.1",
|
"fast-uri": "^3.0.1",
|
||||||
@@ -3242,7 +3242,6 @@
|
|||||||
"version": "11.1.11",
|
"version": "11.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.11.tgz",
|
||||||
"integrity": "sha512-R/+A8XFqLgN8zNs2twhrOaE7dJbRQhdPX3g46am4RT/x8xGLqDphrXkUIno4cGUZHxbczChBAaAPTdPv73wDZA==",
|
"integrity": "sha512-R/+A8XFqLgN8zNs2twhrOaE7dJbRQhdPX3g46am4RT/x8xGLqDphrXkUIno4cGUZHxbczChBAaAPTdPv73wDZA==",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"file-type": "21.2.0",
|
"file-type": "21.2.0",
|
||||||
"iterare": "1.2.1",
|
"iterare": "1.2.1",
|
||||||
@@ -3288,7 +3287,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.11.tgz",
|
||||||
"integrity": "sha512-H9i+zT3RvHi7tDc+lCmWHJ3ustXveABCr+Vcpl96dNOxgmrx4elQSTC4W93Mlav2opfLV+p0UTHY6L+bpUA4zA==",
|
"integrity": "sha512-H9i+zT3RvHi7tDc+lCmWHJ3ustXveABCr+Vcpl96dNOxgmrx4elQSTC4W93Mlav2opfLV+p0UTHY6L+bpUA4zA==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxt/opencollective": "0.4.1",
|
"@nuxt/opencollective": "0.4.1",
|
||||||
"fast-safe-stringify": "2.1.1",
|
"fast-safe-stringify": "2.1.1",
|
||||||
@@ -3368,7 +3366,6 @@
|
|||||||
"version": "11.1.11",
|
"version": "11.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.11.tgz",
|
||||||
"integrity": "sha512-kyABSskdMRIAMWL0SlbwtDy4yn59RL4HDdwHDz/fxWuv7/53YP8Y2DtV3/sHqY5Er0msMVTZrM38MjqXhYL7gw==",
|
"integrity": "sha512-kyABSskdMRIAMWL0SlbwtDy4yn59RL4HDdwHDz/fxWuv7/53YP8Y2DtV3/sHqY5Er0msMVTZrM38MjqXhYL7gw==",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cors": "2.8.5",
|
"cors": "2.8.5",
|
||||||
"express": "5.2.1",
|
"express": "5.2.1",
|
||||||
@@ -3389,7 +3386,6 @@
|
|||||||
"version": "11.1.11",
|
"version": "11.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-11.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-11.1.11.tgz",
|
||||||
"integrity": "sha512-0z6pLg9CuTXtz7q2lRZoPOU94DN28OTa39f4cQrlZysKA6QrKM7w7z6xqb4g32qjF+LQHFNRmMJtE/pLrxBaig==",
|
"integrity": "sha512-0z6pLg9CuTXtz7q2lRZoPOU94DN28OTa39f4cQrlZysKA6QrKM7w7z6xqb4g32qjF+LQHFNRmMJtE/pLrxBaig==",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"socket.io": "4.8.3",
|
"socket.io": "4.8.3",
|
||||||
"tslib": "2.8.1"
|
"tslib": "2.8.1"
|
||||||
@@ -3496,6 +3492,33 @@
|
|||||||
"tslib": "^2.1.0"
|
"tslib": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@nestjs/serve-static": {
|
||||||
|
"version": "5.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nestjs/serve-static/-/serve-static-5.0.4.tgz",
|
||||||
|
"integrity": "sha512-3kO1M9D3vsPyWPFardxIjUYeuolS58PnhCoBTkS7t3BrdZFZCKHnBZ15js+UOzOR2Q6HmD7ssGjLd0DVYVdvOw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"path-to-regexp": "8.3.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@fastify/static": "^8.0.4",
|
||||||
|
"@nestjs/common": "^11.0.2",
|
||||||
|
"@nestjs/core": "^11.0.2",
|
||||||
|
"express": "^5.0.1",
|
||||||
|
"fastify": "^5.2.1"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@fastify/static": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"express": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"fastify": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nestjs/swagger": {
|
"node_modules/@nestjs/swagger": {
|
||||||
"version": "11.2.4",
|
"version": "11.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-11.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-11.2.4.tgz",
|
||||||
@@ -3724,7 +3747,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.22.0.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.22.0.tgz",
|
||||||
"integrity": "sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==",
|
"integrity": "sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.13"
|
"node": ">=16.13"
|
||||||
},
|
},
|
||||||
@@ -3789,7 +3811,6 @@
|
|||||||
"version": "1.6.1",
|
"version": "1.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.1.tgz",
|
||||||
"integrity": "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==",
|
"integrity": "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cluster-key-slot": "1.1.2",
|
"cluster-key-slot": "1.1.2",
|
||||||
"generic-pool": "3.9.0",
|
"generic-pool": "3.9.0",
|
||||||
@@ -4695,7 +4716,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
|
||||||
"integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
|
"integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/estree": "*",
|
"@types/estree": "*",
|
||||||
"@types/json-schema": "*"
|
"@types/json-schema": "*"
|
||||||
@@ -4806,11 +4826,20 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
|
||||||
"integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="
|
"integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/multer": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/multer/-/multer-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-zYZb0+nJhOHtPpGDb3vqPjwpdeGlGC157VpkqNQL+UU2qwoacoQ7MpsAmUptI/0Oa127X32JzWDqQVEXp2RcIA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/express": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "22.19.3",
|
"version": "22.19.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz",
|
||||||
"integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==",
|
"integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.21.0"
|
"undici-types": "~6.21.0"
|
||||||
}
|
}
|
||||||
@@ -4975,7 +5004,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.52.0.tgz",
|
||||||
"integrity": "sha512-iIACsx8pxRnguSYhHiMn2PvhvfpopO9FXHyn1mG5txZIsAaB6F0KwbFnUQN3KCiG3Jcuad/Cao2FAs1Wp7vAyg==",
|
"integrity": "sha512-iIACsx8pxRnguSYhHiMn2PvhvfpopO9FXHyn1mG5txZIsAaB6F0KwbFnUQN3KCiG3Jcuad/Cao2FAs1Wp7vAyg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.52.0",
|
"@typescript-eslint/scope-manager": "8.52.0",
|
||||||
"@typescript-eslint/types": "8.52.0",
|
"@typescript-eslint/types": "8.52.0",
|
||||||
@@ -5613,7 +5641,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -5667,7 +5694,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-deep-equal": "^3.1.1",
|
"fast-deep-equal": "^3.1.1",
|
||||||
"fast-json-stable-stringify": "^2.0.0",
|
"fast-json-stable-stringify": "^2.0.0",
|
||||||
@@ -6157,7 +6183,6 @@
|
|||||||
"url": "https://github.com/sponsors/ai"
|
"url": "https://github.com/sponsors/ai"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.9.0",
|
"baseline-browser-mapping": "^2.9.0",
|
||||||
"caniuse-lite": "^1.0.30001759",
|
"caniuse-lite": "^1.0.30001759",
|
||||||
@@ -6231,7 +6256,6 @@
|
|||||||
"version": "5.66.4",
|
"version": "5.66.4",
|
||||||
"resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.66.4.tgz",
|
"resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.66.4.tgz",
|
||||||
"integrity": "sha512-y2VRk2z7d1YNI2JQDD7iThoD0X/0iZZ3VEp8lqT5s5U0XDl9CIjXp1LQgmE9EKy6ReHtzmYXS1f328PnUbZGtQ==",
|
"integrity": "sha512-y2VRk2z7d1YNI2JQDD7iThoD0X/0iZZ3VEp8lqT5s5U0XDl9CIjXp1LQgmE9EKy6ReHtzmYXS1f328PnUbZGtQ==",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cron-parser": "4.9.0",
|
"cron-parser": "4.9.0",
|
||||||
"ioredis": "5.8.2",
|
"ioredis": "5.8.2",
|
||||||
@@ -6305,7 +6329,6 @@
|
|||||||
"version": "7.2.7",
|
"version": "7.2.7",
|
||||||
"resolved": "https://registry.npmjs.org/cache-manager/-/cache-manager-7.2.7.tgz",
|
"resolved": "https://registry.npmjs.org/cache-manager/-/cache-manager-7.2.7.tgz",
|
||||||
"integrity": "sha512-TKeeb9nSybk1e9E5yAiPVJ6YKdX9FYhwqqy8fBfVKAFVTJYZUNmeIvwjURW6+UikNsO6l2ta27thYgo/oumDsw==",
|
"integrity": "sha512-TKeeb9nSybk1e9E5yAiPVJ6YKdX9FYhwqqy8fBfVKAFVTJYZUNmeIvwjURW6+UikNsO6l2ta27thYgo/oumDsw==",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cacheable/utils": "^2.3.2",
|
"@cacheable/utils": "^2.3.2",
|
||||||
"keyv": "^5.5.4"
|
"keyv": "^5.5.4"
|
||||||
@@ -6457,7 +6480,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"readdirp": "^4.0.1"
|
"readdirp": "^4.0.1"
|
||||||
},
|
},
|
||||||
@@ -6501,14 +6523,12 @@
|
|||||||
"node_modules/class-transformer": {
|
"node_modules/class-transformer": {
|
||||||
"version": "0.5.1",
|
"version": "0.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz",
|
||||||
"integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==",
|
"integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw=="
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/class-validator": {
|
"node_modules/class-validator": {
|
||||||
"version": "0.14.3",
|
"version": "0.14.3",
|
||||||
"resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.3.tgz",
|
"resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.3.tgz",
|
||||||
"integrity": "sha512-rXXekcjofVN1LTOSw+u4u9WXVEUvNBVjORW154q/IdmYWy1nMbOU9aNtZB0t8m+FJQ9q91jlr2f9CwwUFdFMRA==",
|
"integrity": "sha512-rXXekcjofVN1LTOSw+u4u9WXVEUvNBVjORW154q/IdmYWy1nMbOU9aNtZB0t8m+FJQ9q91jlr2f9CwwUFdFMRA==",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/validator": "^13.15.3",
|
"@types/validator": "^13.15.3",
|
||||||
"libphonenumber-js": "^1.11.1",
|
"libphonenumber-js": "^1.11.1",
|
||||||
@@ -7194,7 +7214,8 @@
|
|||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz",
|
||||||
"integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==",
|
"integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/es-object-atoms": {
|
"node_modules/es-object-atoms": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
@@ -7253,7 +7274,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz",
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz",
|
||||||
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.8.0",
|
"@eslint-community/eslint-utils": "^4.8.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
@@ -7313,7 +7333,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz",
|
||||||
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
|
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"eslint-config-prettier": "bin/cli.js"
|
"eslint-config-prettier": "bin/cli.js"
|
||||||
},
|
},
|
||||||
@@ -8674,7 +8693,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz",
|
||||||
"integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==",
|
"integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jest/core": "30.2.0",
|
"@jest/core": "30.2.0",
|
||||||
"@jest/types": "30.2.0",
|
"@jest/types": "30.2.0",
|
||||||
@@ -9518,7 +9536,6 @@
|
|||||||
"version": "5.5.5",
|
"version": "5.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-5.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/keyv/-/keyv-5.5.5.tgz",
|
||||||
"integrity": "sha512-FA5LmZVF1VziNc0bIdCSA1IoSVnDCqE8HJIZZv2/W8YmoAM50+tnUgJR/gQZwEeIMleuIOnRnHA/UaZRNeV4iQ==",
|
"integrity": "sha512-FA5LmZVF1VziNc0bIdCSA1IoSVnDCqE8HJIZZv2/W8YmoAM50+tnUgJR/gQZwEeIMleuIOnRnHA/UaZRNeV4iQ==",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@keyv/serialize": "^1.1.1"
|
"@keyv/serialize": "^1.1.1"
|
||||||
}
|
}
|
||||||
@@ -10263,6 +10280,7 @@
|
|||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
|
||||||
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
|
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
@@ -10446,7 +10464,6 @@
|
|||||||
"version": "0.7.0",
|
"version": "0.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz",
|
||||||
"integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==",
|
"integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"passport-strategy": "1.x.x",
|
"passport-strategy": "1.x.x",
|
||||||
"pause": "0.0.1",
|
"pause": "0.0.1",
|
||||||
@@ -10573,7 +10590,6 @@
|
|||||||
"version": "10.1.0",
|
"version": "10.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/pino/-/pino-10.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/pino/-/pino-10.1.0.tgz",
|
||||||
"integrity": "sha512-0zZC2ygfdqvqK8zJIr1e+wT1T/L+LF6qvqvbzEQ6tiMAoTqEVK9a1K3YRu8HEUvGEvNqZyPJTtb2sNIoTkB83w==",
|
"integrity": "sha512-0zZC2ygfdqvqK8zJIr1e+wT1T/L+LF6qvqvbzEQ6tiMAoTqEVK9a1K3YRu8HEUvGEvNqZyPJTtb2sNIoTkB83w==",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@pinojs/redact": "^0.4.0",
|
"@pinojs/redact": "^0.4.0",
|
||||||
"atomic-sleep": "^1.0.0",
|
"atomic-sleep": "^1.0.0",
|
||||||
@@ -10603,7 +10619,6 @@
|
|||||||
"version": "11.0.0",
|
"version": "11.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/pino-http/-/pino-http-11.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/pino-http/-/pino-http-11.0.0.tgz",
|
||||||
"integrity": "sha512-wqg5XIAGRRIWtTk8qPGxkbrfiwEWz1lgedVLvhLALudKXvg1/L2lTFgTGPJ4Z2e3qcRmxoFxDuSdMdMGNM6I1g==",
|
"integrity": "sha512-wqg5XIAGRRIWtTk8qPGxkbrfiwEWz1lgedVLvhLALudKXvg1/L2lTFgTGPJ4Z2e3qcRmxoFxDuSdMdMGNM6I1g==",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"get-caller-file": "^2.0.5",
|
"get-caller-file": "^2.0.5",
|
||||||
"pino": "^10.0.0",
|
"pino": "^10.0.0",
|
||||||
@@ -10757,7 +10772,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz",
|
||||||
"integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==",
|
"integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"prettier": "bin/prettier.cjs"
|
"prettier": "bin/prettier.cjs"
|
||||||
},
|
},
|
||||||
@@ -10811,7 +10825,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.22.0.tgz",
|
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.22.0.tgz",
|
||||||
"integrity": "sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==",
|
"integrity": "sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/engines": "5.22.0"
|
"@prisma/engines": "5.22.0"
|
||||||
},
|
},
|
||||||
@@ -11876,7 +11889,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
|
||||||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-deep-equal": "^3.1.3",
|
"fast-deep-equal": "^3.1.3",
|
||||||
"fast-uri": "^3.0.1",
|
"fast-uri": "^3.0.1",
|
||||||
@@ -12191,7 +12203,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
|
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
|
||||||
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
|
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cspotcode/source-map-support": "^0.8.0",
|
"@cspotcode/source-map-support": "^0.8.0",
|
||||||
"@tsconfig/node10": "^1.0.7",
|
"@tsconfig/node10": "^1.0.7",
|
||||||
@@ -12329,7 +12340,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -12668,6 +12678,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
|
||||||
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
|
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ajv": "^8.0.0"
|
"ajv": "^8.0.0"
|
||||||
},
|
},
|
||||||
@@ -12685,6 +12696,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
|
||||||
"integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
|
"integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-deep-equal": "^3.1.3"
|
"fast-deep-equal": "^3.1.3"
|
||||||
},
|
},
|
||||||
@@ -12697,6 +12709,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
||||||
"integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
|
"integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esrecurse": "^4.3.0",
|
"esrecurse": "^4.3.0",
|
||||||
"estraverse": "^4.1.1"
|
"estraverse": "^4.1.1"
|
||||||
@@ -12710,6 +12723,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
|
||||||
"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
|
"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4.0"
|
"node": ">=4.0"
|
||||||
}
|
}
|
||||||
@@ -12718,13 +12732,15 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/webpack/node_modules/mime-db": {
|
"node_modules/webpack/node_modules/mime-db": {
|
||||||
"version": "1.52.0",
|
"version": "1.52.0",
|
||||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
@@ -12734,6 +12750,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mime-db": "1.52.0"
|
"mime-db": "1.52.0"
|
||||||
},
|
},
|
||||||
@@ -12746,6 +12763,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz",
|
||||||
"integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==",
|
"integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/json-schema": "^7.0.9",
|
"@types/json-schema": "^7.0.9",
|
||||||
"ajv": "^8.9.0",
|
"ajv": "^8.9.0",
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
"@nestjs/passport": "^11.0.5",
|
"@nestjs/passport": "^11.0.5",
|
||||||
"@nestjs/platform-express": "^11.0.1",
|
"@nestjs/platform-express": "^11.0.1",
|
||||||
"@nestjs/platform-socket.io": "^11.1.11",
|
"@nestjs/platform-socket.io": "^11.1.11",
|
||||||
|
"@nestjs/serve-static": "^5.0.4",
|
||||||
"@nestjs/swagger": "^11.2.4",
|
"@nestjs/swagger": "^11.2.4",
|
||||||
"@nestjs/terminus": "^11.0.0",
|
"@nestjs/terminus": "^11.0.0",
|
||||||
"@nestjs/throttler": "^6.5.0",
|
"@nestjs/throttler": "^6.5.0",
|
||||||
@@ -63,6 +64,7 @@
|
|||||||
"@types/bcrypt": "^6.0.0",
|
"@types/bcrypt": "^6.0.0",
|
||||||
"@types/express": "^5.0.0",
|
"@types/express": "^5.0.0",
|
||||||
"@types/jest": "^30.0.0",
|
"@types/jest": "^30.0.0",
|
||||||
|
"@types/multer": "^2.1.0",
|
||||||
"@types/node": "^22.10.7",
|
"@types/node": "^22.10.7",
|
||||||
"@types/nodemailer": "^7.0.4",
|
"@types/nodemailer": "^7.0.4",
|
||||||
"@types/passport-jwt": "^4.0.1",
|
"@types/passport-jwt": "^4.0.1",
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ generator client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
datasource db {
|
datasource db {
|
||||||
provider = "postgresql"
|
provider = "sqlite"
|
||||||
url = env("DATABASE_URL")
|
url = env("DATABASE_URL")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,3 +160,79 @@ model Translation {
|
|||||||
@@index([locale])
|
@@index([locale])
|
||||||
@@index([namespace])
|
@@index([namespace])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// CMS - Site Content Management
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
model Project {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
title String
|
||||||
|
image String
|
||||||
|
roles String @default("[]") // JSON array stored as string for SQLite
|
||||||
|
color String @default("#FF5733")
|
||||||
|
sortOrder Int @default(0)
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
deletedAt DateTime? // Soft delete support
|
||||||
|
|
||||||
|
@@index([sortOrder])
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// Audit Log — CMS İşlem Kayıtları
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
model AuditLog {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
entity String // "project", "client", "content", "media"
|
||||||
|
entityId String // İlgili kaydın ID'si
|
||||||
|
action String // "CREATE", "UPDATE", "DELETE", "RESTORE"
|
||||||
|
before String? // JSON — önceki durum
|
||||||
|
after String? // JSON — sonraki durum
|
||||||
|
userId String? // İşlemi yapan kullanıcı
|
||||||
|
ip String? // İstek IP'si
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
|
@@index([entity, entityId])
|
||||||
|
@@index([action])
|
||||||
|
@@index([createdAt])
|
||||||
|
}
|
||||||
|
|
||||||
|
model SiteContent {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
section String // "hero", "services", "process", "about", "contact", "footer", "navbar"
|
||||||
|
locale String @default("tr")
|
||||||
|
content String @default("{}") // JSON stored as string for SQLite
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
@@unique([section, locale])
|
||||||
|
@@index([section])
|
||||||
|
@@index([locale])
|
||||||
|
}
|
||||||
|
|
||||||
|
model MediaFile {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
filename String
|
||||||
|
originalName String
|
||||||
|
mimetype String
|
||||||
|
path String
|
||||||
|
url String
|
||||||
|
size Int
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
}
|
||||||
|
|
||||||
|
model Client {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
name String
|
||||||
|
logo String // URL to logo image
|
||||||
|
website String? // Optional website URL
|
||||||
|
sortOrder Int @default(0)
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
@@index([sortOrder])
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
QueryResolver,
|
QueryResolver,
|
||||||
} from 'nestjs-i18n';
|
} from 'nestjs-i18n';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
import { ServeStaticModule } from '@nestjs/serve-static';
|
||||||
|
|
||||||
// Config
|
// Config
|
||||||
import {
|
import {
|
||||||
@@ -39,6 +40,7 @@ import { UsersModule } from './modules/users/users.module';
|
|||||||
import { AdminModule } from './modules/admin/admin.module';
|
import { AdminModule } from './modules/admin/admin.module';
|
||||||
import { HealthModule } from './modules/health/health.module';
|
import { HealthModule } from './modules/health/health.module';
|
||||||
import { GeminiModule } from './modules/gemini/gemini.module';
|
import { GeminiModule } from './modules/gemini/gemini.module';
|
||||||
|
import { CmsModule } from './modules/cms/cms.module';
|
||||||
|
|
||||||
// Guards
|
// Guards
|
||||||
import {
|
import {
|
||||||
@@ -75,11 +77,11 @@ import {
|
|||||||
level: configService.get('app.isDevelopment') ? 'debug' : 'info',
|
level: configService.get('app.isDevelopment') ? 'debug' : 'info',
|
||||||
transport: configService.get('app.isDevelopment')
|
transport: configService.get('app.isDevelopment')
|
||||||
? {
|
? {
|
||||||
target: 'pino-pretty',
|
target: 'pino-pretty',
|
||||||
options: {
|
options: {
|
||||||
singleLine: true,
|
singleLine: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -160,6 +162,15 @@ import {
|
|||||||
// Optional Modules (controlled by env variables)
|
// Optional Modules (controlled by env variables)
|
||||||
GeminiModule,
|
GeminiModule,
|
||||||
HealthModule,
|
HealthModule,
|
||||||
|
|
||||||
|
// CMS Module
|
||||||
|
CmsModule,
|
||||||
|
|
||||||
|
// Serve uploaded files
|
||||||
|
ServeStaticModule.forRoot({
|
||||||
|
rootPath: path.join(__dirname, '..', 'uploads'),
|
||||||
|
serveRoot: '/uploads',
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
// Global Exception Filter
|
// Global Exception Filter
|
||||||
@@ -199,4 +210,4 @@ import {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule { }
|
||||||
|
|||||||
@@ -75,8 +75,13 @@ async function bootstrap() {
|
|||||||
});
|
});
|
||||||
logger.log('Swagger initialized');
|
logger.log('Swagger initialized');
|
||||||
|
|
||||||
logger.log(`Attempting to listen on port ${port}...`);
|
try {
|
||||||
await app.listen(port, '0.0.0.0');
|
logger.log(`Attempting to listen on port ${port}...`);
|
||||||
|
await app.listen(port, '0.0.0.0');
|
||||||
|
} catch (err: any) {
|
||||||
|
logger.error('Failed to bind to port:', err.message, err.stack);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
logger.log('═══════════════════════════════════════════════════════════');
|
logger.log('═══════════════════════════════════════════════════════════');
|
||||||
logger.log(`🚀 Server is running on: http://localhost:${port}/api`);
|
logger.log(`🚀 Server is running on: http://localhost:${port}/api`);
|
||||||
|
|||||||
232
src/modules/cms/cms.controller.ts
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
import {
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
Post,
|
||||||
|
Put,
|
||||||
|
Patch,
|
||||||
|
Delete,
|
||||||
|
Body,
|
||||||
|
Param,
|
||||||
|
Query,
|
||||||
|
UseInterceptors,
|
||||||
|
UploadedFile,
|
||||||
|
ParseFilePipe,
|
||||||
|
MaxFileSizeValidator,
|
||||||
|
FileTypeValidator,
|
||||||
|
OnModuleInit,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { FileInterceptor } from '@nestjs/platform-express';
|
||||||
|
import { ApiTags, ApiBearerAuth, ApiConsumes, ApiBody } from '@nestjs/swagger';
|
||||||
|
import { diskStorage } from 'multer';
|
||||||
|
import { extname } from 'path';
|
||||||
|
import { CmsService } from './cms.service';
|
||||||
|
import {
|
||||||
|
CreateProjectDto,
|
||||||
|
UpdateProjectDto,
|
||||||
|
ReorderProjectsDto,
|
||||||
|
UpdateSiteContentDto,
|
||||||
|
CreateClientDto,
|
||||||
|
UpdateClientDto,
|
||||||
|
} from './dto';
|
||||||
|
import { Public, Roles } from '../../common/decorators';
|
||||||
|
|
||||||
|
// Generate unique filename
|
||||||
|
const storage = diskStorage({
|
||||||
|
destination: './uploads',
|
||||||
|
filename: (req, file, cb) => {
|
||||||
|
const uniqueName = `${Date.now()}-${Math.round(Math.random() * 1e9)}${extname(file.originalname)}`;
|
||||||
|
cb(null, uniqueName);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
@ApiTags('CMS')
|
||||||
|
@Controller('cms')
|
||||||
|
export class CmsController implements OnModuleInit {
|
||||||
|
constructor(private readonly cmsService: CmsService) { }
|
||||||
|
|
||||||
|
async onModuleInit() {
|
||||||
|
await this.cmsService.seedDefaultProjects();
|
||||||
|
await this.cmsService.seedAdminUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Public Endpoints ────────────────────────────
|
||||||
|
|
||||||
|
@Public()
|
||||||
|
@Get('projects')
|
||||||
|
findAllProjects() {
|
||||||
|
return this.cmsService.findAllProjects();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Public()
|
||||||
|
@Get('content')
|
||||||
|
findAllContent(@Query('locale') locale?: string) {
|
||||||
|
return this.cmsService.findAllContent(locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Public()
|
||||||
|
@Get('content/:section')
|
||||||
|
findContentBySection(
|
||||||
|
@Param('section') section: string,
|
||||||
|
@Query('locale') locale?: string,
|
||||||
|
) {
|
||||||
|
return this.cmsService.findContentBySection(section, locale ?? 'tr');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Admin: Projects ─────────────────────────────
|
||||||
|
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Roles('admin')
|
||||||
|
@Post('projects')
|
||||||
|
createProject(@Body() dto: CreateProjectDto) {
|
||||||
|
return this.cmsService.createProject(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Roles('admin')
|
||||||
|
@Put('projects/:id')
|
||||||
|
updateProject(@Param('id') id: string, @Body() dto: UpdateProjectDto) {
|
||||||
|
return this.cmsService.updateProject(id, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Roles('admin')
|
||||||
|
@Delete('projects/:id')
|
||||||
|
deleteProject(@Param('id') id: string) {
|
||||||
|
return this.cmsService.deleteProject(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Roles('admin')
|
||||||
|
@Post('projects/:id/restore')
|
||||||
|
restoreProject(@Param('id') id: string) {
|
||||||
|
return this.cmsService.restoreProject(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Roles('admin')
|
||||||
|
@Patch('projects/reorder')
|
||||||
|
reorderProjects(@Body() dto: ReorderProjectsDto) {
|
||||||
|
return this.cmsService.reorderProjects(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Admin: Site Content ─────────────────────────
|
||||||
|
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Roles('admin')
|
||||||
|
@Put('content/:section')
|
||||||
|
upsertContent(
|
||||||
|
@Param('section') section: string,
|
||||||
|
@Query('locale') locale: string = 'tr',
|
||||||
|
@Body() dto: UpdateSiteContentDto,
|
||||||
|
) {
|
||||||
|
return this.cmsService.upsertContent(section, locale, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Admin: Media Upload ─────────────────────────
|
||||||
|
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Roles('admin')
|
||||||
|
@Post('upload')
|
||||||
|
@UseInterceptors(FileInterceptor('file', { storage }))
|
||||||
|
@ApiConsumes('multipart/form-data')
|
||||||
|
@ApiBody({
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: { file: { type: 'string', format: 'binary' } },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
async uploadFile(
|
||||||
|
@UploadedFile(
|
||||||
|
new ParseFilePipe({
|
||||||
|
validators: [
|
||||||
|
new FileTypeValidator({ fileType: /image\/(jpeg|jpg|png|webp|gif|svg\+xml)/ }),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
file: Express.Multer.File,
|
||||||
|
) {
|
||||||
|
const url = `/uploads/${file.filename}`;
|
||||||
|
const media = await this.cmsService.createMediaFile({
|
||||||
|
filename: file.filename,
|
||||||
|
originalName: file.originalname,
|
||||||
|
mimetype: file.mimetype,
|
||||||
|
path: file.path,
|
||||||
|
url,
|
||||||
|
size: file.size,
|
||||||
|
});
|
||||||
|
return media;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Public()
|
||||||
|
@Get('media')
|
||||||
|
findAllMedia() {
|
||||||
|
return this.cmsService.findAllMedia();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Roles('admin')
|
||||||
|
@Delete('media/:id')
|
||||||
|
deleteMedia(@Param('id') id: string) {
|
||||||
|
return this.cmsService.deleteMedia(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Public: Clients ─────────────────────────
|
||||||
|
|
||||||
|
@Public()
|
||||||
|
@Get('clients')
|
||||||
|
findAllClients() {
|
||||||
|
return this.cmsService.findAllClients();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Admin: Clients ─────────────────────────
|
||||||
|
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Roles('admin')
|
||||||
|
@Post('clients')
|
||||||
|
createClient(@Body() dto: CreateClientDto) {
|
||||||
|
return this.cmsService.createClient(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Roles('admin')
|
||||||
|
@Put('clients/:id')
|
||||||
|
updateClient(@Param('id') id: string, @Body() dto: UpdateClientDto) {
|
||||||
|
return this.cmsService.updateClient(id, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Roles('admin')
|
||||||
|
@Delete('clients/:id')
|
||||||
|
deleteClient(@Param('id') id: string) {
|
||||||
|
return this.cmsService.deleteClient(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Admin: Audit Logs ───────────────────────
|
||||||
|
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Roles('admin')
|
||||||
|
@Get('audit-logs')
|
||||||
|
findAuditLogs(
|
||||||
|
@Query('entity') entity?: string,
|
||||||
|
@Query('action') action?: string,
|
||||||
|
@Query('limit') limit?: string,
|
||||||
|
@Query('offset') offset?: string,
|
||||||
|
) {
|
||||||
|
return this.cmsService.findAuditLogs({
|
||||||
|
entity,
|
||||||
|
action,
|
||||||
|
limit: limit ? parseInt(limit, 10) : 50,
|
||||||
|
offset: offset ? parseInt(offset, 10) : 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Roles('admin')
|
||||||
|
@Get('audit-logs/:entity/:entityId')
|
||||||
|
findEntityAuditLogs(
|
||||||
|
@Param('entity') entity: string,
|
||||||
|
@Param('entityId') entityId: string,
|
||||||
|
) {
|
||||||
|
return this.cmsService.findAuditLogsByEntity(entity, entityId);
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/modules/cms/cms.module.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { CmsController } from './cms.controller';
|
||||||
|
import { CmsService } from './cms.service';
|
||||||
|
import { DatabaseModule } from '../../database/database.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [DatabaseModule],
|
||||||
|
controllers: [CmsController],
|
||||||
|
providers: [CmsService],
|
||||||
|
exports: [CmsService],
|
||||||
|
})
|
||||||
|
export class CmsModule { }
|
||||||
456
src/modules/cms/cms.service.ts
Normal file
@@ -0,0 +1,456 @@
|
|||||||
|
import { Injectable, Logger, NotFoundException, InternalServerErrorException } from '@nestjs/common';
|
||||||
|
import * as bcrypt from 'bcrypt';
|
||||||
|
import { PrismaService } from '../../database/prisma.service';
|
||||||
|
import {
|
||||||
|
CreateProjectDto,
|
||||||
|
UpdateProjectDto,
|
||||||
|
ReorderProjectsDto,
|
||||||
|
UpdateSiteContentDto,
|
||||||
|
CreateClientDto,
|
||||||
|
UpdateClientDto,
|
||||||
|
} from './dto';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CmsService {
|
||||||
|
private readonly logger = new Logger(CmsService.name);
|
||||||
|
|
||||||
|
constructor(private readonly prisma: PrismaService) { }
|
||||||
|
|
||||||
|
// ── Audit Logging ──────────────────────────────
|
||||||
|
|
||||||
|
private async audit(
|
||||||
|
entity: string,
|
||||||
|
entityId: string,
|
||||||
|
action: string,
|
||||||
|
before?: any,
|
||||||
|
after?: any,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
await this.prisma.auditLog.create({
|
||||||
|
data: {
|
||||||
|
entity,
|
||||||
|
entityId,
|
||||||
|
action,
|
||||||
|
before: before ? JSON.stringify(before) : null,
|
||||||
|
after: after ? JSON.stringify(after) : null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.logger.log(`[AUDIT] ${action} ${entity}:${entityId}`);
|
||||||
|
} catch (err) {
|
||||||
|
// Audit failure should never break the main operation
|
||||||
|
this.logger.error(`[AUDIT] Failed to log ${action} ${entity}:${entityId}`, err.stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Write Verification ─────────────────────────
|
||||||
|
|
||||||
|
private async verifyWrite<T extends { id: string }>(
|
||||||
|
model: string,
|
||||||
|
id: string,
|
||||||
|
operation: string,
|
||||||
|
): Promise<T> {
|
||||||
|
const record = await (this.prisma as any)[model].findUnique({ where: { id } });
|
||||||
|
if (!record) {
|
||||||
|
this.logger.error(`[VERIFY] ${operation} failed — record ${model}:${id} not found after write`);
|
||||||
|
throw new InternalServerErrorException(
|
||||||
|
`Veritabanı yazma doğrulaması başarısız: ${model}:${id} yazıldıktan sonra bulunamadı`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.logger.debug(`[VERIFY] ${operation} ${model}:${id} — OK`);
|
||||||
|
return record as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Projects ──────────────────────────────────
|
||||||
|
|
||||||
|
async findAllProjects() {
|
||||||
|
return this.prisma.project.findMany({
|
||||||
|
where: { isActive: true, deletedAt: null },
|
||||||
|
orderBy: { sortOrder: 'asc' },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async findProjectById(id: string) {
|
||||||
|
const project = await this.prisma.project.findUnique({ where: { id } });
|
||||||
|
if (!project || project.deletedAt) throw new NotFoundException('Project not found');
|
||||||
|
return project;
|
||||||
|
}
|
||||||
|
|
||||||
|
async createProject(dto: CreateProjectDto) {
|
||||||
|
// Auto-set sortOrder to last position
|
||||||
|
const lastProject = await this.prisma.project.findFirst({
|
||||||
|
where: { deletedAt: null },
|
||||||
|
orderBy: { sortOrder: 'desc' },
|
||||||
|
});
|
||||||
|
const nextOrder = (lastProject?.sortOrder ?? -1) + 1;
|
||||||
|
|
||||||
|
const created = await this.prisma.project.create({
|
||||||
|
data: {
|
||||||
|
title: dto.title,
|
||||||
|
image: dto.image,
|
||||||
|
roles: JSON.stringify(dto.roles),
|
||||||
|
color: dto.color ?? '#FF5733',
|
||||||
|
sortOrder: dto.sortOrder ?? nextOrder,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Write verification
|
||||||
|
const verified = await this.verifyWrite('project', created.id, 'CREATE Project');
|
||||||
|
|
||||||
|
// Audit log
|
||||||
|
await this.audit('project', created.id, 'CREATE', null, verified);
|
||||||
|
|
||||||
|
return verified;
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateProject(id: string, dto: UpdateProjectDto) {
|
||||||
|
const before = await this.findProjectById(id); // throws if not found
|
||||||
|
|
||||||
|
const updated = await this.prisma.project.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
...dto,
|
||||||
|
roles: dto.roles ? JSON.stringify(dto.roles) : undefined,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Write verification
|
||||||
|
const verified = await this.verifyWrite('project', id, 'UPDATE Project');
|
||||||
|
|
||||||
|
// Audit log
|
||||||
|
await this.audit('project', id, 'UPDATE', before, verified);
|
||||||
|
|
||||||
|
return verified;
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteProject(id: string) {
|
||||||
|
const before = await this.findProjectById(id);
|
||||||
|
|
||||||
|
// Soft delete — projeyi silmek yerine deletedAt işaretle
|
||||||
|
const deleted = await this.prisma.project.update({
|
||||||
|
where: { id },
|
||||||
|
data: { deletedAt: new Date() },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Write verification — deletedAt'ın set edildiğini doğrula
|
||||||
|
const verified = await (this.prisma as any).project.findUnique({ where: { id } });
|
||||||
|
if (!verified || !verified.deletedAt) {
|
||||||
|
this.logger.error(`[VERIFY] Soft DELETE failed — project:${id} deletedAt is still null`);
|
||||||
|
throw new InternalServerErrorException('Silme doğrulaması başarısız');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Audit log
|
||||||
|
await this.audit('project', id, 'DELETE', before, null);
|
||||||
|
|
||||||
|
return deleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
async restoreProject(id: string) {
|
||||||
|
const project = await this.prisma.project.findUnique({ where: { id } });
|
||||||
|
if (!project) throw new NotFoundException('Project not found');
|
||||||
|
if (!project.deletedAt) throw new NotFoundException('Project is not deleted');
|
||||||
|
|
||||||
|
const restored = await this.prisma.project.update({
|
||||||
|
where: { id },
|
||||||
|
data: { deletedAt: null },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Write verification
|
||||||
|
const verified = await this.verifyWrite('project', id, 'RESTORE Project');
|
||||||
|
|
||||||
|
// Audit log
|
||||||
|
await this.audit('project', id, 'RESTORE', null, verified);
|
||||||
|
|
||||||
|
return verified;
|
||||||
|
}
|
||||||
|
|
||||||
|
async reorderProjects(dto: ReorderProjectsDto) {
|
||||||
|
const updates = dto.items.map((item) =>
|
||||||
|
this.prisma.project.update({
|
||||||
|
where: { id: item.id },
|
||||||
|
data: { sortOrder: item.sortOrder },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
await this.prisma.$transaction(updates);
|
||||||
|
return this.findAllProjects();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Site Content ──────────────────────────────
|
||||||
|
|
||||||
|
async findAllContent(locale?: string) {
|
||||||
|
const where = locale ? { locale } : {};
|
||||||
|
const contents = await this.prisma.siteContent.findMany({ where });
|
||||||
|
|
||||||
|
// Transform into a section-keyed object
|
||||||
|
const result: Record<string, any> = {};
|
||||||
|
for (const c of contents) {
|
||||||
|
if (!result[c.locale]) result[c.locale] = {};
|
||||||
|
try {
|
||||||
|
result[c.locale][c.section] = JSON.parse(c.content);
|
||||||
|
} catch {
|
||||||
|
result[c.locale][c.section] = c.content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async findContentBySection(section: string, locale: string = 'tr') {
|
||||||
|
const content = await this.prisma.siteContent.findUnique({
|
||||||
|
where: { section_locale: { section, locale } },
|
||||||
|
});
|
||||||
|
if (!content) return null;
|
||||||
|
try {
|
||||||
|
return JSON.parse(content.content);
|
||||||
|
} catch {
|
||||||
|
return content.content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async upsertContent(
|
||||||
|
section: string,
|
||||||
|
locale: string,
|
||||||
|
dto: UpdateSiteContentDto,
|
||||||
|
) {
|
||||||
|
const contentStr = JSON.stringify(dto.content);
|
||||||
|
|
||||||
|
// Get before state for audit
|
||||||
|
const before = await this.prisma.siteContent.findUnique({
|
||||||
|
where: { section_locale: { section, locale } },
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await this.prisma.siteContent.upsert({
|
||||||
|
where: { section_locale: { section, locale } },
|
||||||
|
create: {
|
||||||
|
section,
|
||||||
|
locale,
|
||||||
|
content: contentStr,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
content: contentStr,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Audit log
|
||||||
|
await this.audit(
|
||||||
|
'content',
|
||||||
|
result.id,
|
||||||
|
before ? 'UPDATE' : 'CREATE',
|
||||||
|
before,
|
||||||
|
result,
|
||||||
|
);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Media Files ──────────────────────────────
|
||||||
|
|
||||||
|
async createMediaFile(file: {
|
||||||
|
filename: string;
|
||||||
|
originalName: string;
|
||||||
|
mimetype: string;
|
||||||
|
path: string;
|
||||||
|
url: string;
|
||||||
|
size: number;
|
||||||
|
}) {
|
||||||
|
const created = await this.prisma.mediaFile.create({ data: file });
|
||||||
|
|
||||||
|
// Audit log
|
||||||
|
await this.audit('media', created.id, 'CREATE', null, created);
|
||||||
|
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAllMedia() {
|
||||||
|
return this.prisma.mediaFile.findMany({
|
||||||
|
orderBy: { createdAt: 'desc' },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteMedia(id: string) {
|
||||||
|
const media = await this.prisma.mediaFile.findUnique({ where: { id } });
|
||||||
|
if (!media) throw new NotFoundException('Media not found');
|
||||||
|
|
||||||
|
const result = await this.prisma.mediaFile.delete({ where: { id } });
|
||||||
|
|
||||||
|
// Audit log
|
||||||
|
await this.audit('media', id, 'DELETE', media, null);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Seed ──────────────────────────────────────
|
||||||
|
|
||||||
|
async seedDefaultProjects() {
|
||||||
|
const count = await this.prisma.project.count({
|
||||||
|
where: { deletedAt: null },
|
||||||
|
});
|
||||||
|
if (count > 0) {
|
||||||
|
this.logger.log('Projects already seeded, skipping');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaults = [
|
||||||
|
{
|
||||||
|
title: 'Deadpool',
|
||||||
|
image: 'https://picsum.photos/seed/deadpool/1920/1080?blur=2',
|
||||||
|
roles: JSON.stringify(['Official Turkish Voice', 'Character Voice Acting']),
|
||||||
|
color: '#FF5733',
|
||||||
|
sortOrder: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Spider-Man',
|
||||||
|
image: 'https://picsum.photos/seed/spiderman/1920/1080?blur=2',
|
||||||
|
roles: JSON.stringify(['Official Turkish Voice', 'Character Voice Acting']),
|
||||||
|
color: '#C70039',
|
||||||
|
sortOrder: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'The Lion King',
|
||||||
|
image: 'https://picsum.photos/seed/lionking/1920/1080?blur=2',
|
||||||
|
roles: JSON.stringify(['Simba', 'Musical Performance', 'Vocals']),
|
||||||
|
color: '#900C3F',
|
||||||
|
sortOrder: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Monster Notebook',
|
||||||
|
image: 'https://picsum.photos/seed/monster/1920/1080?blur=2',
|
||||||
|
roles: JSON.stringify(['Brand Face', 'Corporate Voice']),
|
||||||
|
color: '#511845',
|
||||||
|
sortOrder: 3,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const p of defaults) {
|
||||||
|
const created = await this.prisma.project.create({ data: p });
|
||||||
|
await this.audit('project', created.id, 'CREATE', null, created);
|
||||||
|
}
|
||||||
|
this.logger.log(`Seeded ${defaults.length} default projects`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async seedAdminUser() {
|
||||||
|
const adminExists = await this.prisma.user.findUnique({
|
||||||
|
where: { email: 'admin@haruncan.com' },
|
||||||
|
});
|
||||||
|
if (adminExists) {
|
||||||
|
this.logger.log('Admin user already exists, skipping');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hashedPassword = await bcrypt.hash('admin123', 10);
|
||||||
|
|
||||||
|
await this.prisma.user.create({
|
||||||
|
data: {
|
||||||
|
email: 'admin@haruncan.com',
|
||||||
|
password: hashedPassword,
|
||||||
|
firstName: 'Harun',
|
||||||
|
lastName: 'CAN',
|
||||||
|
roles: {
|
||||||
|
create: {
|
||||||
|
role: {
|
||||||
|
connectOrCreate: {
|
||||||
|
where: { name: 'admin' },
|
||||||
|
create: { name: 'admin', description: 'Administrator' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.logger.log('✅ Admin user created: admin@haruncan.com / admin123');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Clients ──────────────────────────────────
|
||||||
|
|
||||||
|
async findAllClients() {
|
||||||
|
return this.prisma.client.findMany({
|
||||||
|
where: { isActive: true },
|
||||||
|
orderBy: { sortOrder: 'asc' },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async createClient(dto: CreateClientDto) {
|
||||||
|
const lastClient = await this.prisma.client.findFirst({
|
||||||
|
orderBy: { sortOrder: 'desc' },
|
||||||
|
});
|
||||||
|
const nextOrder = (lastClient?.sortOrder ?? -1) + 1;
|
||||||
|
|
||||||
|
const created = await this.prisma.client.create({
|
||||||
|
data: {
|
||||||
|
name: dto.name,
|
||||||
|
logo: dto.logo,
|
||||||
|
website: dto.website,
|
||||||
|
sortOrder: dto.sortOrder ?? nextOrder,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Write verification
|
||||||
|
const verified = await this.verifyWrite('client', created.id, 'CREATE Client');
|
||||||
|
|
||||||
|
// Audit log
|
||||||
|
await this.audit('client', created.id, 'CREATE', null, verified);
|
||||||
|
|
||||||
|
return verified;
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateClient(id: string, dto: UpdateClientDto) {
|
||||||
|
const before = await this.prisma.client.findUniqueOrThrow({ where: { id } }).catch(() => {
|
||||||
|
throw new NotFoundException('Client not found');
|
||||||
|
});
|
||||||
|
|
||||||
|
const updated = await this.prisma.client.update({ where: { id }, data: dto });
|
||||||
|
|
||||||
|
// Write verification
|
||||||
|
const verified = await this.verifyWrite('client', id, 'UPDATE Client');
|
||||||
|
|
||||||
|
// Audit log
|
||||||
|
await this.audit('client', id, 'UPDATE', before, verified);
|
||||||
|
|
||||||
|
return verified;
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteClient(id: string) {
|
||||||
|
const client = await this.prisma.client.findUnique({ where: { id } });
|
||||||
|
if (!client) throw new NotFoundException('Client not found');
|
||||||
|
|
||||||
|
const result = await this.prisma.client.delete({ where: { id } });
|
||||||
|
|
||||||
|
// Audit log
|
||||||
|
await this.audit('client', id, 'DELETE', client, null);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Audit Logs (Admin Queries) ───────────────
|
||||||
|
|
||||||
|
async findAuditLogs(options?: {
|
||||||
|
entity?: string;
|
||||||
|
entityId?: string;
|
||||||
|
action?: string;
|
||||||
|
limit?: number;
|
||||||
|
offset?: number;
|
||||||
|
}) {
|
||||||
|
const where: any = {};
|
||||||
|
if (options?.entity) where.entity = options.entity;
|
||||||
|
if (options?.entityId) where.entityId = options.entityId;
|
||||||
|
if (options?.action) where.action = options.action;
|
||||||
|
|
||||||
|
const [logs, total] = await Promise.all([
|
||||||
|
this.prisma.auditLog.findMany({
|
||||||
|
where,
|
||||||
|
orderBy: { createdAt: 'desc' },
|
||||||
|
take: options?.limit ?? 50,
|
||||||
|
skip: options?.offset ?? 0,
|
||||||
|
}),
|
||||||
|
this.prisma.auditLog.count({ where }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return { logs, total };
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAuditLogsByEntity(entity: string, entityId: string) {
|
||||||
|
return this.prisma.auditLog.findMany({
|
||||||
|
where: { entity, entityId },
|
||||||
|
orderBy: { createdAt: 'desc' },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
142
src/modules/cms/dto/cms.dto.ts
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
import {
|
||||||
|
IsString,
|
||||||
|
IsOptional,
|
||||||
|
IsArray,
|
||||||
|
IsInt,
|
||||||
|
IsBoolean,
|
||||||
|
IsObject,
|
||||||
|
Min,
|
||||||
|
} from 'class-validator';
|
||||||
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
// ── Projects ──────────────────────────────────────
|
||||||
|
|
||||||
|
export class CreateProjectDto {
|
||||||
|
@ApiProperty({ example: 'Deadpool' })
|
||||||
|
@IsString()
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 'https://example.com/deadpool.jpg' })
|
||||||
|
@IsString()
|
||||||
|
image: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: ['Official Turkish Voice', 'Character Voice Acting'] })
|
||||||
|
@IsArray()
|
||||||
|
@IsString({ each: true })
|
||||||
|
roles: string[];
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ example: '#FF5733' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
color?: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ example: 0 })
|
||||||
|
@IsOptional()
|
||||||
|
@IsInt()
|
||||||
|
@Min(0)
|
||||||
|
sortOrder?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UpdateProjectDto {
|
||||||
|
@ApiPropertyOptional({ example: 'Deadpool 3' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
title?: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ example: 'https://example.com/deadpool3.jpg' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
image?: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ example: ['Official Turkish Voice'] })
|
||||||
|
@IsOptional()
|
||||||
|
@IsArray()
|
||||||
|
@IsString({ each: true })
|
||||||
|
roles?: string[];
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ example: '#C70039' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
color?: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ example: 1 })
|
||||||
|
@IsOptional()
|
||||||
|
@IsInt()
|
||||||
|
@Min(0)
|
||||||
|
sortOrder?: number;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ example: true })
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
isActive?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ReorderProjectsDto {
|
||||||
|
@ApiProperty({
|
||||||
|
example: [
|
||||||
|
{ id: 'uuid-1', sortOrder: 0 },
|
||||||
|
{ id: 'uuid-2', sortOrder: 1 },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
@IsArray()
|
||||||
|
items: { id: string; sortOrder: number }[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Site Content ──────────────────────────────────
|
||||||
|
|
||||||
|
export class UpdateSiteContentDto {
|
||||||
|
@ApiProperty({ example: { title: 'Hello', desc: 'World' } })
|
||||||
|
@IsObject()
|
||||||
|
content: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Clients ──────────────────────────────────────
|
||||||
|
|
||||||
|
export class CreateClientDto {
|
||||||
|
@ApiProperty({ example: 'Netflix' })
|
||||||
|
@IsString()
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 'https://example.com/netflix-logo.png' })
|
||||||
|
@IsString()
|
||||||
|
logo: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ example: 'https://netflix.com' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
website?: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ example: 0 })
|
||||||
|
@IsOptional()
|
||||||
|
@IsInt()
|
||||||
|
@Min(0)
|
||||||
|
sortOrder?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UpdateClientDto {
|
||||||
|
@ApiPropertyOptional({ example: 'Netflix' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
name?: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ example: 'https://example.com/netflix-logo.png' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
logo?: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ example: 'https://netflix.com' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
website?: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ example: 0 })
|
||||||
|
@IsOptional()
|
||||||
|
@IsInt()
|
||||||
|
@Min(0)
|
||||||
|
sortOrder?: number;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ example: true })
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
isActive?: boolean;
|
||||||
|
}
|
||||||
1
src/modules/cms/dto/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './cms.dto';
|
||||||
BIN
uploads/1772836705449-386698498.webp
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
uploads/1772836714460-810290801.webp
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
uploads/1772836777451-720750819.webp
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
uploads/1772836785630-554541704.webp
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
uploads/1772836812306-24774814.webp
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
uploads/1772836820013-985207017.webp
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
uploads/1772836836133-281285727.webp
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
uploads/1772836867451-316497790.webp
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
uploads/1772982194781-173304442.webp
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
BIN
uploads/1772983229310-723555621.webp
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
uploads/1772983501530-2875755.webp
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
uploads/1772983860203-545700454.webp
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
uploads/1772984524655-837143941.png
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
uploads/1772985263057-412119294.png
Normal file
|
After Width: | Height: | Size: 70 B |
BIN
uploads/1772985271261-647774470.webp
Normal file
|
After Width: | Height: | Size: 44 B |
1
uploads/1772985293317-376544079.jpg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
dummy
|
||||||
BIN
uploads/1772985457669-609882992.webp
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
uploads/1772985607638-95283599.png
Normal file
|
After Width: | Height: | Size: 70 KiB |