backup
This commit is contained in:
1 parent
5a01897a44
commit
62b6a26a4f
18 files changed
+1391
-160
No files matched your search
Generated
+242
-8
@@ -9,15 +9,24 @@
|
||||
"version": "0.0.1",
|
||||
"license": "UNLICENSED",
|
||||
"dependencies": {
|
||||
"@nestjs/cache-manager": "^3.0.1",
|
||||
"@nestjs/common": "^11.0.1",
|
||||
"@nestjs/config": "^4.0.2",
|
||||
"@nestjs/core": "^11.0.1",
|
||||
"@nestjs/jwt": "^11.0.0",
|
||||
"@nestjs/platform-express": "^11.0.1",
|
||||
"@nestjs/schedule": "^5.0.1",
|
||||
"@prisma/client": "^6.6.0",
|
||||
"@types/bcrypt": "^5.0.2",
|
||||
"@types/passport-jwt": "^4.0.1",
|
||||
"bcrypt": "^5.1.1",
|
||||
"bullmq": "^5.47.2",
|
||||
"cache-manager": "^6.4.2",
|
||||
"cache-manager-redis-store": "^3.0.1",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.1",
|
||||
"express-rate-limit": "^7.5.0",
|
||||
"helmet": "^8.1.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"prisma": "^6.6.0",
|
||||
"redis": "^4.7.0",
|
||||
@@ -2331,6 +2340,39 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@keyv/serialize": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.0.3.tgz",
|
||||
"integrity": "sha512-qnEovoOp5Np2JDGonIDL6Ayihw0RhnRh6vxPuHo4RDn1UOzwEo4AeIfpL6UGIrsceWrCMiVPgwRjbHu4vYFc3g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer": "^6.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@keyv/serialize/node_modules/buffer": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@lukeed/csprng": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz",
|
||||
@@ -2767,6 +2809,19 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/cache-manager": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/cache-manager/-/cache-manager-3.0.1.tgz",
|
||||
"integrity": "sha512-4UxTnR0fsmKL5YDalU2eLFVnL+OBebWUpX+hEduKGncrVKH4PPNoiRn1kXyOCjmzb0UvWgqubpssNouc8e0MCw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@nestjs/common": "^9.0.0 || ^10.0.0 || ^11.0.0",
|
||||
"@nestjs/core": "^9.0.0 || ^10.0.0 || ^11.0.0",
|
||||
"cache-manager": ">=6",
|
||||
"keyv": ">=5",
|
||||
"rxjs": "^7.8.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/cli": {
|
||||
"version": "11.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-11.0.6.tgz",
|
||||
@@ -3025,6 +3080,21 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/config": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/config/-/config-4.0.2.tgz",
|
||||
"integrity": "sha512-McMW6EXtpc8+CwTUwFdg6h7dYcBUpH5iUILCclAsa+MbCEvC9ZKu4dCHRlJqALuhjLw97pbQu62l4+wRwGeZqA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dotenv": "16.4.7",
|
||||
"dotenv-expand": "12.0.1",
|
||||
"lodash": "4.17.21"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nestjs/common": "^10.0.0 || ^11.0.0",
|
||||
"rxjs": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/core": {
|
||||
"version": "11.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.0.13.tgz",
|
||||
@@ -3100,6 +3170,19 @@
|
||||
"@nestjs/core": "^11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/schedule": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-5.0.1.tgz",
|
||||
"integrity": "sha512-kFoel84I4RyS2LNPH6yHYTKxB16tb3auAEciFuc788C3ph6nABkUfzX5IE+unjVaRX+3GuruJwurNepMlHXpQg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cron": "3.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nestjs/common": "^10.0.0 || ^11.0.0",
|
||||
"@nestjs/core": "^10.0.0 || ^11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/schematics": {
|
||||
"version": "11.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-11.0.4.tgz",
|
||||
@@ -3955,6 +4038,12 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/luxon": {
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz",
|
||||
"integrity": "sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/methods": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz",
|
||||
@@ -4070,6 +4159,12 @@
|
||||
"@types/superagent": "^8.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/validator": {
|
||||
"version": "13.12.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.12.3.tgz",
|
||||
"integrity": "sha512-2ipwZ2NydGQJImne+FhNdhgRM37e9lCev99KnqkbFHd94Xn/mErARWI1RSLem1QA19ch5kOhzIZd7e8CA2FI8g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/yargs": {
|
||||
"version": "17.0.33",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
|
||||
@@ -5108,7 +5203,6 @@
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -5382,6 +5476,27 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/cache-manager": {
|
||||
"version": "6.4.2",
|
||||
"resolved": "https://registry.npmjs.org/cache-manager/-/cache-manager-6.4.2.tgz",
|
||||
"integrity": "sha512-oT0d1cGWZAlqEGDPjOfhmldTS767jT6kBT3KIdn7MX5OevlRVYqJT+LxRv5WY4xW9heJtYxeRRXaoKlEW+Biew==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"keyv": "^5.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/cache-manager-redis-store": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cache-manager-redis-store/-/cache-manager-redis-store-3.0.1.tgz",
|
||||
"integrity": "sha512-o560kw+dFqusC9lQJhcm6L2F2fMKobJ5af+FoR2PdnMVdpQ3f3Bz6qzvObTGyvoazQJxjQNWgMQeChP4vRTuXQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"redis": "^4.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 16.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cacheable-lookup": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz",
|
||||
@@ -5411,6 +5526,16 @@
|
||||
"node": ">=14.16"
|
||||
}
|
||||
},
|
||||
"node_modules/cacheable-request/node_modules/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
"integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"json-buffer": "3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
@@ -5573,6 +5698,23 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/class-transformer": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz",
|
||||
"integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"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",
|
||||
"dependencies": {
|
||||
"@types/validator": "^13.11.8",
|
||||
"libphonenumber-js": "^1.10.53",
|
||||
"validator": "^13.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cli-cursor": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
|
||||
@@ -5962,6 +6104,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cron": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/cron/-/cron-3.5.0.tgz",
|
||||
"integrity": "sha512-0eYZqCnapmxYcV06uktql93wNWdlTmmBFP2iYz+JPVcQqlyFYcn1lFuIk4R54pkOmE7mcldTAPZv6X5XA4Q46A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/luxon": "~3.4.0",
|
||||
"luxon": "~3.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cron-parser": {
|
||||
"version": "4.9.0",
|
||||
"resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz",
|
||||
@@ -5974,6 +6126,15 @@
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cron/node_modules/luxon": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.5.0.tgz",
|
||||
"integrity": "sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
@@ -6174,6 +6335,33 @@
|
||||
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "16.4.7",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
|
||||
"integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://dotenvx.com"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv-expand": {
|
||||
"version": "12.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.1.tgz",
|
||||
"integrity": "sha512-LaKRbou8gt0RNID/9RoI+J2rvXsBRPMV7p+ElHlPhcSARbCPDYcYG2s1TIzAfWv4YSgyY5taidWzzs31lNV3yQ==",
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"dotenv": "^16.4.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://dotenvx.com"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
@@ -6759,6 +6947,21 @@
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/express-rate-limit": {
|
||||
"version": "7.5.0",
|
||||
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz",
|
||||
"integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/express-rate-limit"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"express": "^4.11 || 5 || ^5.0.0-beta.1"
|
||||
}
|
||||
},
|
||||
"node_modules/express/node_modules/content-disposition": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
|
||||
@@ -7135,6 +7338,16 @@
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/flat-cache/node_modules/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
"integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"json-buffer": "3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/flatted": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
|
||||
@@ -7688,6 +7901,15 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/helmet": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz",
|
||||
"integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/hexoid": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hexoid/-/hexoid-2.0.0.tgz",
|
||||
@@ -7781,7 +8003,6 @@
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -8979,13 +9200,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
"integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
|
||||
"dev": true,
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-5.3.2.tgz",
|
||||
"integrity": "sha512-Lji2XRxqqa5Wg+CHLVfFKBImfJZ4pCSccu9eVWK6w4c2SDFLd8JAn1zqTuSFnsxb7ope6rMsnIHfp+eBbRBRZQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"json-buffer": "3.0.1"
|
||||
"@keyv/serialize": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/kind-of": {
|
||||
@@ -9032,6 +9252,12 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/libphonenumber-js": {
|
||||
"version": "1.12.6",
|
||||
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.6.tgz",
|
||||
"integrity": "sha512-PJiS4ETaUfCOFLpmtKzAbqZQjCCKVu2OhTV4SVNNE7c2nu/dACvtCqj4L0i/KWNnIgRv7yrILvBj5Lonv5Ncxw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lines-and-columns": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||
@@ -9069,7 +9295,6 @@
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.defaults": {
|
||||
@@ -12295,6 +12520,15 @@
|
||||
"node": ">=10.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/validator": {
|
||||
"version": "13.15.0",
|
||||
"resolved": "https://registry.npmjs.org/validator/-/validator-13.15.0.tgz",
|
||||
"integrity": "sha512-36B2ryl4+oL5QxZ3AzD0t5SsMNGvTtQHpjgFO5tbNxfXbMFkY822ktCDe1MnlqV3301QQI9SLHDNJokDI+Z9pA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
|
||||
@@ -20,15 +20,24 @@
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/cache-manager": "^3.0.1",
|
||||
"@nestjs/common": "^11.0.1",
|
||||
"@nestjs/config": "^4.0.2",
|
||||
"@nestjs/core": "^11.0.1",
|
||||
"@nestjs/jwt": "^11.0.0",
|
||||
"@nestjs/platform-express": "^11.0.1",
|
||||
"@nestjs/schedule": "^5.0.1",
|
||||
"@prisma/client": "^6.6.0",
|
||||
"@types/bcrypt": "^5.0.2",
|
||||
"@types/passport-jwt": "^4.0.1",
|
||||
"bcrypt": "^5.1.1",
|
||||
"bullmq": "^5.47.2",
|
||||
"cache-manager": "^6.4.2",
|
||||
"cache-manager-redis-store": "^3.0.1",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.1",
|
||||
"express-rate-limit": "^7.5.0",
|
||||
"helmet": "^8.1.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"prisma": "^6.6.0",
|
||||
"redis": "^4.7.0",
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ScheduleModule } from '@nestjs/schedule';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
import { ArticlesModule } from './articles/articles.module';
|
||||
import { AuthModule } from './auth/auth.module';
|
||||
import { RssModule } from './rss/rss.module';
|
||||
import { CollectionsModule } from './collections/collections.module';
|
||||
import { RedisCacheModule } from './cache/cache.module';
|
||||
|
||||
@Module({
|
||||
imports: [ArticlesModule, AuthModule, RssModule, CollectionsModule],
|
||||
imports: [
|
||||
ScheduleModule.forRoot(),
|
||||
ArticlesModule,
|
||||
AuthModule,
|
||||
RssModule,
|
||||
CollectionsModule,
|
||||
RedisCacheModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
})
|
||||
|
||||
Vendored
+22
@@ -0,0 +1,22 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { CacheModule } from '@nestjs/cache-manager';
|
||||
import * as redisStore from 'cache-manager-redis-store';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
CacheModule.registerAsync({
|
||||
imports: [ConfigModule],
|
||||
inject: [ConfigService],
|
||||
useFactory: (configService: ConfigService) => ({
|
||||
store: redisStore,
|
||||
host: configService.get('REDIS_HOST', 'localhost'),
|
||||
port: configService.get('REDIS_PORT', 6379),
|
||||
ttl: 60 * 60 * 24, // 24 hours
|
||||
}),
|
||||
}),
|
||||
],
|
||||
exports: [CacheModule],
|
||||
})
|
||||
export class RedisCacheModule {}
|
||||
|
||||
@@ -6,10 +6,14 @@ import {
|
||||
Param,
|
||||
UseGuards,
|
||||
Request,
|
||||
Query,
|
||||
Inject,
|
||||
} from '@nestjs/common';
|
||||
import { CACHE_MANAGER } from '@nestjs/cache-manager';
|
||||
import { Cache } from 'cache-manager';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { JwtAuthGuard } from '../auth';
|
||||
import { Article, Collection, QuickNote } from '@prisma/client';
|
||||
import { CreateCollectionDto, AddArticleDto, CreateQuickNoteDto } from './dto';
|
||||
|
||||
interface RequestWithUser extends Request {
|
||||
user: {
|
||||
@@ -18,59 +22,63 @@ interface RequestWithUser extends Request {
|
||||
};
|
||||
}
|
||||
|
||||
type CollectionWithRelations = Collection & {
|
||||
articles: Article[];
|
||||
quickNotes: QuickNote[];
|
||||
notes: any[];
|
||||
};
|
||||
|
||||
interface CreateCollectionDto {
|
||||
name: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
interface CreateArticleDto {
|
||||
title: string;
|
||||
content: string;
|
||||
source: string;
|
||||
url: string;
|
||||
publishedAt: Date;
|
||||
}
|
||||
|
||||
interface CreateQuickNoteDto {
|
||||
title: string;
|
||||
content: string;
|
||||
shared?: boolean;
|
||||
sharedVia?: string;
|
||||
}
|
||||
|
||||
@Controller('api/collections')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
export class CollectionsController {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
@Inject(CACHE_MANAGER) private cacheManager: Cache,
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
async getAllCollections(
|
||||
@Request() req: RequestWithUser,
|
||||
): Promise<CollectionWithRelations[]> {
|
||||
return await this.prisma.collection.findMany({
|
||||
@Query('limit') limit?: number,
|
||||
@Query('cursor') cursor?: string,
|
||||
) {
|
||||
const cacheKey = `collections:${req.user.id}:${limit}:${cursor}`;
|
||||
const cached = await this.cacheManager.get(cacheKey);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const take = limit ? Number(limit) : 20;
|
||||
const collections = await this.prisma.collection.findMany({
|
||||
where: {
|
||||
userId: req.user.id,
|
||||
},
|
||||
take: take + 1,
|
||||
skip: cursor ? 1 : 0,
|
||||
cursor: cursor ? { id: cursor } : undefined,
|
||||
orderBy: [{ updatedAt: 'desc' }, { id: 'desc' }],
|
||||
include: {
|
||||
articles: true,
|
||||
quickNotes: true,
|
||||
notes: true,
|
||||
},
|
||||
});
|
||||
|
||||
let nextCursor: string | null = null;
|
||||
if (collections.length > take) {
|
||||
const nextItem = collections.pop()!;
|
||||
nextCursor = nextItem.id;
|
||||
}
|
||||
|
||||
const result = {
|
||||
collections,
|
||||
nextCursor,
|
||||
};
|
||||
|
||||
await this.cacheManager.set(cacheKey, result, 300); // Cache for 5 minutes
|
||||
return result;
|
||||
}
|
||||
|
||||
@Post()
|
||||
async createCollection(
|
||||
@Body() data: CreateCollectionDto,
|
||||
@Request() req: RequestWithUser,
|
||||
): Promise<CollectionWithRelations> {
|
||||
return await this.prisma.collection.create({
|
||||
) {
|
||||
const collection = await this.prisma.collection.create({
|
||||
data: {
|
||||
...data,
|
||||
userId: req.user.id,
|
||||
@@ -81,13 +89,20 @@ export class CollectionsController {
|
||||
notes: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Invalidate user's collections cache
|
||||
const cachePattern = `collections:${req.user.id}:*`;
|
||||
await this.cacheManager.del(cachePattern);
|
||||
|
||||
return collection;
|
||||
}
|
||||
|
||||
@Post(':id/add-article')
|
||||
async addArticleToCollection(
|
||||
@Param('id') id: string,
|
||||
@Body() articleData: CreateArticleDto,
|
||||
): Promise<Article> {
|
||||
@Body() articleData: AddArticleDto,
|
||||
@Request() req: RequestWithUser,
|
||||
) {
|
||||
const existingArticle = await this.prisma.article.findUnique({
|
||||
where: { url: articleData.url },
|
||||
});
|
||||
@@ -101,10 +116,15 @@ export class CollectionsController {
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Invalidate cache for this collection
|
||||
const cachePattern = `collections:${req.user.id}:*`;
|
||||
await this.cacheManager.del(cachePattern);
|
||||
|
||||
return existingArticle;
|
||||
}
|
||||
|
||||
return await this.prisma.article.create({
|
||||
const article = await this.prisma.article.create({
|
||||
data: {
|
||||
...articleData,
|
||||
collections: {
|
||||
@@ -112,6 +132,12 @@ export class CollectionsController {
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Invalidate cache for this collection
|
||||
const cachePattern = `collections:${req.user.id}:*`;
|
||||
await this.cacheManager.del(cachePattern);
|
||||
|
||||
return article;
|
||||
}
|
||||
|
||||
@Post(':id/quick-notes')
|
||||
@@ -119,8 +145,8 @@ export class CollectionsController {
|
||||
@Param('id') id: string,
|
||||
@Body() quickNoteData: CreateQuickNoteDto,
|
||||
@Request() req: RequestWithUser,
|
||||
): Promise<QuickNote> {
|
||||
return await this.prisma.quickNote.create({
|
||||
) {
|
||||
const quickNote = await this.prisma.quickNote.create({
|
||||
data: {
|
||||
...quickNoteData,
|
||||
shared: quickNoteData.shared ?? false,
|
||||
@@ -132,5 +158,11 @@ export class CollectionsController {
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Invalidate cache for this collection
|
||||
const cachePattern = `collections:${req.user.id}:*`;
|
||||
await this.cacheManager.del(cachePattern);
|
||||
|
||||
return quickNote;
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,10 @@ import { Module } from '@nestjs/common';
|
||||
import { PrismaModule } from '../prisma/prisma.module';
|
||||
import { CollectionsController } from './collections.controller';
|
||||
import { AuthModule } from '../auth/auth.module';
|
||||
import { RedisCacheModule } from '../cache/cache.module';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, AuthModule],
|
||||
imports: [PrismaModule, AuthModule, RedisCacheModule],
|
||||
controllers: [CollectionsController],
|
||||
})
|
||||
export class CollectionsModule {}
|
||||
@@ -0,0 +1,63 @@
|
||||
import {
|
||||
IsString,
|
||||
IsOptional,
|
||||
IsUUID,
|
||||
IsNotEmpty,
|
||||
Length,
|
||||
} from 'class-validator';
|
||||
|
||||
export class CreateCollectionDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@Length(1, 100)
|
||||
name: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Length(0, 500)
|
||||
description?: string;
|
||||
|
||||
@IsUUID()
|
||||
userId: string;
|
||||
}
|
||||
|
||||
export class AddArticleDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
title: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
content: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
source: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
url: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
publishedAt: string;
|
||||
}
|
||||
|
||||
export class CreateQuickNoteDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@Length(1, 100)
|
||||
title: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@Length(1, 5000)
|
||||
content: string;
|
||||
|
||||
@IsOptional()
|
||||
shared?: boolean;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
sharedVia?: string;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import {
|
||||
ExceptionFilter,
|
||||
Catch,
|
||||
ArgumentsHost,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
} from '@nestjs/common';
|
||||
import { Response } from 'express';
|
||||
|
||||
@Catch()
|
||||
export class HttpExceptionFilter implements ExceptionFilter {
|
||||
catch(exception: unknown, host: ArgumentsHost) {
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse<Response>();
|
||||
|
||||
const status =
|
||||
exception instanceof HttpException
|
||||
? exception.getStatus()
|
||||
: HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
|
||||
const message =
|
||||
exception instanceof HttpException
|
||||
? exception.message
|
||||
: 'Internal server error';
|
||||
|
||||
const errorResponse = {
|
||||
statusCode: status,
|
||||
message,
|
||||
timestamp: new Date().toISOString(),
|
||||
path: ctx.getRequest().url,
|
||||
};
|
||||
|
||||
// Log error for debugging
|
||||
console.error('Exception:', exception);
|
||||
console.error('Stack trace:', (exception as Error).stack);
|
||||
|
||||
response.status(status).json(errorResponse);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,37 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
import { HttpExceptionFilter } from './filters/http-exception.filter';
|
||||
import helmet from 'helmet';
|
||||
import { ValidationPipe } from '@nestjs/common';
|
||||
import rateLimit from 'express-rate-limit';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
|
||||
// Global exception filter
|
||||
app.useGlobalFilters(new HttpExceptionFilter());
|
||||
|
||||
// Global validation pipe
|
||||
app.useGlobalPipes(
|
||||
new ValidationPipe({
|
||||
whitelist: true,
|
||||
transform: true,
|
||||
forbidNonWhitelisted: true,
|
||||
}),
|
||||
);
|
||||
|
||||
// Security headers
|
||||
app.use(helmet());
|
||||
|
||||
// Rate limiting
|
||||
app.use(
|
||||
rateLimit({
|
||||
windowMs: 15 * 60 * 1000, // 15 minutes
|
||||
max: 100, // limit each IP to 100 requests per windowMs
|
||||
message: 'Too many requests from this IP, please try again later',
|
||||
}),
|
||||
);
|
||||
|
||||
await app.listen(process.env.PORT ?? 3000);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ScheduleModule } from '@nestjs/schedule';
|
||||
import { PrismaModule } from '../prisma/prisma.module';
|
||||
import { RssService } from './rss.service';
|
||||
import { RssController } from './rss.controller';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
imports: [PrismaModule, ScheduleModule],
|
||||
providers: [RssService],
|
||||
controllers: [RssController],
|
||||
exports: [RssService],
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { Cron, CronExpression } from '@nestjs/schedule';
|
||||
import * as Parser from 'rss-parser';
|
||||
|
||||
@Injectable()
|
||||
export class RssService {
|
||||
private feedUrls: string[] = [
|
||||
'https://rt.com/rss',
|
||||
'http://feeds.abcnews.com',
|
||||
'http://rss.cnn.com/rss/cnn',
|
||||
'https://www.npr.org/rss/',
|
||||
'https://www.theguardian.com/rss',
|
||||
'https://www.bbc.co.uk/news',
|
||||
];
|
||||
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
private parser: Parser;
|
||||
@@ -20,6 +30,25 @@ export class RssService {
|
||||
});
|
||||
}
|
||||
|
||||
addFeedUrl(url: string) {
|
||||
if (!this.feedUrls.includes(url)) {
|
||||
this.feedUrls.push(url);
|
||||
}
|
||||
}
|
||||
|
||||
@Cron(CronExpression.EVERY_5_MINUTES)
|
||||
async handleCron() {
|
||||
console.log('Fetching RSS feeds...', Date.now().toLocaleString());
|
||||
for (const url of this.feedUrls) {
|
||||
try {
|
||||
await this.fetchAndStoreArticles(url);
|
||||
console.log(`Successfully fetched articles from ${url}`);
|
||||
} catch (error) {
|
||||
console.error(`Error fetching from ${url}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private cleanHtml(html: string): string {
|
||||
// Remove HTML tags and decode entities
|
||||
return html
|
||||
@@ -36,7 +65,11 @@ export class RssService {
|
||||
|
||||
for (const item of feed.items) {
|
||||
// Extract content from either contentEncoded or description
|
||||
const rawContent = item.contentEncoded || item.content || item.description || '';
|
||||
const rawContent =
|
||||
(item as Parser.Item & { contentEncoded?: string }).contentEncoded ||
|
||||
item.content ||
|
||||
item.description ||
|
||||
'';
|
||||
const cleanContent = this.cleanHtml(rawContent);
|
||||
|
||||
// Get the first image URL from content if available
|
||||
@@ -44,7 +77,9 @@ export class RssService {
|
||||
const imageUrl = imageMatch ? imageMatch[1] : null;
|
||||
|
||||
// Extract categories and authors
|
||||
const categories = Array.isArray(item.categories) ? item.categories : [];
|
||||
const categories = Array.isArray(item.categories)
|
||||
? item.categories
|
||||
: [];
|
||||
const authors = item.creator ? [item.creator] : [];
|
||||
|
||||
await this.prisma.article.upsert({
|
||||
|
||||
Reference in new issue
Block a user