251125:0000 Phase 6 wait start dev Check
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
"dependencies": {
|
||||
"@casl/ability": "^6.7.3",
|
||||
"@elastic/elasticsearch": "^8.11.1",
|
||||
"@nestjs-modules/ioredis": "^2.0.2",
|
||||
"@nestjs/axios": "^4.0.1",
|
||||
"@nestjs/bullmq": "^11.0.4",
|
||||
"@nestjs/cache-manager": "^3.0.1",
|
||||
|
||||
470
backend/pnpm-lock.yaml
generated
470
backend/pnpm-lock.yaml
generated
@@ -14,6 +14,12 @@ importers:
|
||||
'@elastic/elasticsearch':
|
||||
specifier: ^8.11.1
|
||||
version: 8.19.1
|
||||
'@nestjs-modules/ioredis':
|
||||
specifier: ^2.0.2
|
||||
version: 2.0.2(@nestjs/axios@4.0.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.2)(rxjs@7.8.2))(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@nestjs/typeorm@11.0.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))))(ioredis@5.8.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))
|
||||
'@nestjs/axios':
|
||||
specifier: ^4.0.1
|
||||
version: 4.0.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.2)(rxjs@7.8.2)
|
||||
'@nestjs/bullmq':
|
||||
specifier: ^11.0.4
|
||||
version: 11.0.4(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(bullmq@5.63.2)
|
||||
@@ -53,6 +59,9 @@ importers:
|
||||
'@nestjs/swagger':
|
||||
specifier: ^11.2.3
|
||||
version: 11.2.3(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)
|
||||
'@nestjs/terminus':
|
||||
specifier: ^11.0.0
|
||||
version: 11.0.0(@nestjs/axios@4.0.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.2)(rxjs@7.8.2))(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@nestjs/typeorm@11.0.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))))(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))
|
||||
'@nestjs/throttler':
|
||||
specifier: ^6.4.0
|
||||
version: 6.4.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2)
|
||||
@@ -71,6 +80,9 @@ importers:
|
||||
ajv-formats:
|
||||
specifier: ^3.0.1
|
||||
version: 3.0.1(ajv@8.17.1)
|
||||
async-retry:
|
||||
specifier: ^1.3.3
|
||||
version: 1.3.3
|
||||
axios:
|
||||
specifier: ^1.13.2
|
||||
version: 1.13.2
|
||||
@@ -110,15 +122,24 @@ importers:
|
||||
mysql2:
|
||||
specifier: ^3.15.3
|
||||
version: 3.15.3
|
||||
nest-winston:
|
||||
specifier: ^1.10.2
|
||||
version: 1.10.2(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(winston@3.18.3)
|
||||
nodemailer:
|
||||
specifier: ^7.0.10
|
||||
version: 7.0.10
|
||||
opossum:
|
||||
specifier: ^9.0.0
|
||||
version: 9.0.0
|
||||
passport:
|
||||
specifier: ^0.7.0
|
||||
version: 0.7.0
|
||||
passport-jwt:
|
||||
specifier: ^4.0.1
|
||||
version: 4.0.1
|
||||
prom-client:
|
||||
specifier: ^15.1.3
|
||||
version: 15.1.3
|
||||
redlock:
|
||||
specifier: 5.0.0-beta.2
|
||||
version: 5.0.0-beta.2
|
||||
@@ -140,6 +161,9 @@ importers:
|
||||
uuid:
|
||||
specifier: ^13.0.0
|
||||
version: 13.0.0
|
||||
winston:
|
||||
specifier: ^3.18.3
|
||||
version: 3.18.3
|
||||
devDependencies:
|
||||
'@eslint/eslintrc':
|
||||
specifier: ^3.2.0
|
||||
@@ -156,6 +180,9 @@ importers:
|
||||
'@nestjs/testing':
|
||||
specifier: ^11.0.1
|
||||
version: 11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@nestjs/platform-express@11.1.9)
|
||||
'@types/async-retry':
|
||||
specifier: ^1.4.9
|
||||
version: 1.4.9
|
||||
'@types/bcrypt':
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0
|
||||
@@ -180,6 +207,9 @@ importers:
|
||||
'@types/node':
|
||||
specifier: ^22.10.7
|
||||
version: 22.19.1
|
||||
'@types/opossum':
|
||||
specifier: ^8.1.9
|
||||
version: 8.1.9
|
||||
'@types/passport-jwt':
|
||||
specifier: ^4.0.1
|
||||
version: 4.0.1
|
||||
@@ -572,10 +602,17 @@ packages:
|
||||
resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
|
||||
engines: {node: '>=0.1.90'}
|
||||
|
||||
'@colors/colors@1.6.0':
|
||||
resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==}
|
||||
engines: {node: '>=0.1.90'}
|
||||
|
||||
'@cspotcode/source-map-support@0.8.1':
|
||||
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
'@dabh/diagnostics@2.0.8':
|
||||
resolution: {integrity: sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==}
|
||||
|
||||
'@elastic/elasticsearch@8.19.1':
|
||||
resolution: {integrity: sha512-+1j9NnQVOX+lbWB8LhCM7IkUmjU05Y4+BmSLfusq0msCsQb1Va+OUKFCoOXjCJqQrcgdRdQCjYYyolQ/npQALQ==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -980,6 +1017,20 @@ packages:
|
||||
'@napi-rs/wasm-runtime@0.2.12':
|
||||
resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
|
||||
|
||||
'@nestjs-modules/ioredis@2.0.2':
|
||||
resolution: {integrity: sha512-8pzSvT8R3XP6p8ZzQmEN8OnY0yWrJ/elFhwQK+PID2zf1SLBkAZ18bDcx3SKQ2atledt0gd9kBeP5xT4MlyS7Q==}
|
||||
peerDependencies:
|
||||
'@nestjs/common': '>=6.7.0'
|
||||
'@nestjs/core': '>=6.7.0'
|
||||
ioredis: '>=5.0.0'
|
||||
|
||||
'@nestjs/axios@4.0.1':
|
||||
resolution: {integrity: sha512-68pFJgu+/AZbWkGu65Z3r55bTsCPlgyKaV4BSG8yUAD72q1PPuyVRgUwFv6BxdnibTUHlyxm06FmYWNC+bjN7A==}
|
||||
peerDependencies:
|
||||
'@nestjs/common': ^10.0.0 || ^11.0.0
|
||||
axios: ^1.3.1
|
||||
rxjs: ^7.0.0
|
||||
|
||||
'@nestjs/bull-shared@11.0.4':
|
||||
resolution: {integrity: sha512-VBJcDHSAzxQnpcDfA0kt9MTGUD1XZzfByV70su0W0eDCQ9aqIEBlzWRW21tv9FG9dIut22ysgDidshdjlnczLw==}
|
||||
peerDependencies:
|
||||
@@ -1124,6 +1175,102 @@ packages:
|
||||
class-validator:
|
||||
optional: true
|
||||
|
||||
'@nestjs/terminus@10.2.0':
|
||||
resolution: {integrity: sha512-zPs98xvJ4ogEimRQOz8eU90mb7z+W/kd/mL4peOgrJ/VqER+ibN2Cboj65uJZW3XuNhpOqaeYOJte86InJd44A==}
|
||||
peerDependencies:
|
||||
'@grpc/grpc-js': '*'
|
||||
'@grpc/proto-loader': '*'
|
||||
'@mikro-orm/core': '*'
|
||||
'@mikro-orm/nestjs': '*'
|
||||
'@nestjs/axios': ^1.0.0 || ^2.0.0 || ^3.0.0
|
||||
'@nestjs/common': ^9.0.0 || ^10.0.0
|
||||
'@nestjs/core': ^9.0.0 || ^10.0.0
|
||||
'@nestjs/microservices': ^9.0.0 || ^10.0.0
|
||||
'@nestjs/mongoose': ^9.0.0 || ^10.0.0
|
||||
'@nestjs/sequelize': ^9.0.0 || ^10.0.0
|
||||
'@nestjs/typeorm': ^9.0.0 || ^10.0.0
|
||||
'@prisma/client': '*'
|
||||
mongoose: '*'
|
||||
reflect-metadata: 0.1.x
|
||||
rxjs: 7.x
|
||||
sequelize: '*'
|
||||
typeorm: '*'
|
||||
peerDependenciesMeta:
|
||||
'@grpc/grpc-js':
|
||||
optional: true
|
||||
'@grpc/proto-loader':
|
||||
optional: true
|
||||
'@mikro-orm/core':
|
||||
optional: true
|
||||
'@mikro-orm/nestjs':
|
||||
optional: true
|
||||
'@nestjs/axios':
|
||||
optional: true
|
||||
'@nestjs/microservices':
|
||||
optional: true
|
||||
'@nestjs/mongoose':
|
||||
optional: true
|
||||
'@nestjs/sequelize':
|
||||
optional: true
|
||||
'@nestjs/typeorm':
|
||||
optional: true
|
||||
'@prisma/client':
|
||||
optional: true
|
||||
mongoose:
|
||||
optional: true
|
||||
sequelize:
|
||||
optional: true
|
||||
typeorm:
|
||||
optional: true
|
||||
|
||||
'@nestjs/terminus@11.0.0':
|
||||
resolution: {integrity: sha512-c55LOo9YGovmQHtFUMa/vDaxGZ2cglMTZejqgHREaApt/GArTfgYYGwhRXPLq8ZwiQQlLuYB+79e9iA8mlDSLA==}
|
||||
peerDependencies:
|
||||
'@grpc/grpc-js': '*'
|
||||
'@grpc/proto-loader': '*'
|
||||
'@mikro-orm/core': '*'
|
||||
'@mikro-orm/nestjs': '*'
|
||||
'@nestjs/axios': ^2.0.0 || ^3.0.0 || ^4.0.0
|
||||
'@nestjs/common': ^10.0.0 || ^11.0.0
|
||||
'@nestjs/core': ^10.0.0 || ^11.0.0
|
||||
'@nestjs/microservices': ^10.0.0 || ^11.0.0
|
||||
'@nestjs/mongoose': ^11.0.0
|
||||
'@nestjs/sequelize': ^10.0.0 || ^11.0.0
|
||||
'@nestjs/typeorm': ^10.0.0 || ^11.0.0
|
||||
'@prisma/client': '*'
|
||||
mongoose: '*'
|
||||
reflect-metadata: 0.1.x || 0.2.x
|
||||
rxjs: 7.x
|
||||
sequelize: '*'
|
||||
typeorm: '*'
|
||||
peerDependenciesMeta:
|
||||
'@grpc/grpc-js':
|
||||
optional: true
|
||||
'@grpc/proto-loader':
|
||||
optional: true
|
||||
'@mikro-orm/core':
|
||||
optional: true
|
||||
'@mikro-orm/nestjs':
|
||||
optional: true
|
||||
'@nestjs/axios':
|
||||
optional: true
|
||||
'@nestjs/microservices':
|
||||
optional: true
|
||||
'@nestjs/mongoose':
|
||||
optional: true
|
||||
'@nestjs/sequelize':
|
||||
optional: true
|
||||
'@nestjs/typeorm':
|
||||
optional: true
|
||||
'@prisma/client':
|
||||
optional: true
|
||||
mongoose:
|
||||
optional: true
|
||||
sequelize:
|
||||
optional: true
|
||||
typeorm:
|
||||
optional: true
|
||||
|
||||
'@nestjs/testing@11.1.9':
|
||||
resolution: {integrity: sha512-UFxerBDdb0RUNxQNj25pvkvNE7/vxKhXYWBt3QuwBFnYISzRIzhVlyIqLfoV5YI3zV0m0Nn4QAn1KM0zzwfEng==}
|
||||
peerDependencies:
|
||||
@@ -1424,6 +1571,9 @@ packages:
|
||||
resolution: {integrity: sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@so-ric/colorspace@1.1.6':
|
||||
resolution: {integrity: sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==}
|
||||
|
||||
'@socket.io/component-emitter@3.1.2':
|
||||
resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
|
||||
|
||||
@@ -1458,6 +1608,9 @@ packages:
|
||||
'@tybys/wasm-util@0.10.1':
|
||||
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
|
||||
|
||||
'@types/async-retry@1.4.9':
|
||||
resolution: {integrity: sha512-s1ciZQJzRh3708X/m3vPExr5KJlzlZJvXsKpbtE2luqNcbROr64qU+3KpJsYHqWMeaxI839OvXf9PrUSw1Xtyg==}
|
||||
|
||||
'@types/babel__core@7.20.5':
|
||||
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
|
||||
|
||||
@@ -1565,6 +1718,9 @@ packages:
|
||||
'@types/nodemailer@7.0.4':
|
||||
resolution: {integrity: sha512-ee8fxWqOchH+Hv6MDDNNy028kwvVnLplrStm4Zf/3uHWw5zzo8FoYYeffpJtGs2wWysEumMH0ZIdMGMY1eMAow==}
|
||||
|
||||
'@types/opossum@8.1.9':
|
||||
resolution: {integrity: sha512-Jm/tYxuJFefiwRYs+/EOsUP3ktk0c8siMgAHPLnA4PXF4wKghzcjqf88dY+Xii5jId5Txw4JV0FMKTpjbd7KJA==}
|
||||
|
||||
'@types/passport-jwt@4.0.1':
|
||||
resolution: {integrity: sha512-Y0Ykz6nWP4jpxgEUYq8NoVZeCQPo1ZndJLfapI249g1jHChvRfZRO/LS3tqu26YgAS/laI1qx98sYGz0IalRXQ==}
|
||||
|
||||
@@ -1580,6 +1736,9 @@ packages:
|
||||
'@types/range-parser@1.2.7':
|
||||
resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==}
|
||||
|
||||
'@types/retry@0.12.5':
|
||||
resolution: {integrity: sha512-3xSjTp3v03X/lSQLkczaN9UIEwJMoMCA1+Nb5HfbJEQWogdeQIyVtTvxPXDQjZ5zws8rFQfVfRdz03ARihPJgw==}
|
||||
|
||||
'@types/send@0.17.6':
|
||||
resolution: {integrity: sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==}
|
||||
|
||||
@@ -1598,6 +1757,9 @@ packages:
|
||||
'@types/supertest@6.0.3':
|
||||
resolution: {integrity: sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==}
|
||||
|
||||
'@types/triple-beam@1.3.5':
|
||||
resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==}
|
||||
|
||||
'@types/uuid@11.0.0':
|
||||
resolution: {integrity: sha512-HVyk8nj2m+jcFRNazzqyVKiZezyhDKrGUA3jlEcg/nZ6Ms+qHwocba1Y/AaVaznJTAM9xpdFSh+ptbNrhOGvZA==}
|
||||
deprecated: This is a stub types definition. uuid provides its own type definitions, so you do not need this installed.
|
||||
@@ -1891,6 +2053,9 @@ packages:
|
||||
ajv@8.17.1:
|
||||
resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}
|
||||
|
||||
ansi-align@3.0.1:
|
||||
resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==}
|
||||
|
||||
ansi-colors@4.1.3:
|
||||
resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -1961,6 +2126,12 @@ packages:
|
||||
asap@2.0.6:
|
||||
resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==}
|
||||
|
||||
async-retry@1.3.3:
|
||||
resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==}
|
||||
|
||||
async@3.2.6:
|
||||
resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==}
|
||||
|
||||
asynckit@0.4.0:
|
||||
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||
|
||||
@@ -2018,6 +2189,9 @@ packages:
|
||||
resolution: {integrity: sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==}
|
||||
engines: {node: '>= 18'}
|
||||
|
||||
bintrees@1.0.2:
|
||||
resolution: {integrity: sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==}
|
||||
|
||||
bl@4.1.0:
|
||||
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
|
||||
|
||||
@@ -2028,6 +2202,10 @@ packages:
|
||||
bowser@2.12.1:
|
||||
resolution: {integrity: sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw==}
|
||||
|
||||
boxen@5.1.2:
|
||||
resolution: {integrity: sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
brace-expansion@1.1.12:
|
||||
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
|
||||
|
||||
@@ -2127,6 +2305,10 @@ packages:
|
||||
chardet@2.1.1:
|
||||
resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==}
|
||||
|
||||
check-disk-space@3.4.0:
|
||||
resolution: {integrity: sha512-drVkSqfwA+TvuEhFipiR1OC9boEGZL5RrWvVsOthdcvQNXyCCuKkEiTOTXZ7qxSf/GLwq4GvzfrQD/Wz325hgw==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
chokidar@4.0.3:
|
||||
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
|
||||
engines: {node: '>= 14.16.0'}
|
||||
@@ -2148,6 +2330,10 @@ packages:
|
||||
class-validator@0.14.2:
|
||||
resolution: {integrity: sha512-3kMVRF2io8N8pY1IFIXlho9r8IPUUIfHe2hYVtiebvAzU2XeQFXTv+XI4WX+TnXmtwXMDcjngcpkiPM0O9PvLw==}
|
||||
|
||||
cli-boxes@2.2.1:
|
||||
resolution: {integrity: sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
cli-cursor@3.1.0:
|
||||
resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -2187,9 +2373,25 @@ packages:
|
||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||
engines: {node: '>=7.0.0'}
|
||||
|
||||
color-convert@3.1.3:
|
||||
resolution: {integrity: sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==}
|
||||
engines: {node: '>=14.6'}
|
||||
|
||||
color-name@1.1.4:
|
||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||
|
||||
color-name@2.1.0:
|
||||
resolution: {integrity: sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==}
|
||||
engines: {node: '>=12.20'}
|
||||
|
||||
color-string@2.1.4:
|
||||
resolution: {integrity: sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
color@5.0.3:
|
||||
resolution: {integrity: sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
combined-stream@1.0.8:
|
||||
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
@@ -2393,6 +2595,9 @@ packages:
|
||||
emoji-regex@9.2.2:
|
||||
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
|
||||
|
||||
enabled@2.0.0:
|
||||
resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==}
|
||||
|
||||
encodeurl@2.0.0:
|
||||
resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
@@ -2580,6 +2785,9 @@ packages:
|
||||
fb-watchman@2.0.2:
|
||||
resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==}
|
||||
|
||||
fecha@4.2.3:
|
||||
resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==}
|
||||
|
||||
fflate@0.8.2:
|
||||
resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
|
||||
|
||||
@@ -2626,6 +2834,9 @@ packages:
|
||||
flatted@3.3.3:
|
||||
resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
|
||||
|
||||
fn.name@1.1.0:
|
||||
resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==}
|
||||
|
||||
follow-redirects@1.15.11:
|
||||
resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==}
|
||||
engines: {node: '>=4.0'}
|
||||
@@ -3142,6 +3353,9 @@ packages:
|
||||
keyv@5.5.4:
|
||||
resolution: {integrity: sha512-eohl3hKTiVyD1ilYdw9T0OiB4hnjef89e3dMYKz+mVKDzj+5IteTseASUsOB+EU9Tf6VNTCjDePcP6wkDGmLKQ==}
|
||||
|
||||
kuler@2.0.0:
|
||||
resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==}
|
||||
|
||||
leven@3.1.0:
|
||||
resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -3218,6 +3432,10 @@ packages:
|
||||
resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
logform@2.7.0:
|
||||
resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
|
||||
long@5.3.2:
|
||||
resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==}
|
||||
|
||||
@@ -3383,6 +3601,12 @@ packages:
|
||||
neo-async@2.6.2:
|
||||
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
|
||||
|
||||
nest-winston@1.10.2:
|
||||
resolution: {integrity: sha512-Z9IzL/nekBOF/TEwBHUJDiDPMaXUcFquUQOFavIRet6xF0EbuWnOzslyN/ksgzG+fITNgXhMdrL/POp9SdaFxA==}
|
||||
peerDependencies:
|
||||
'@nestjs/common': ^5.0.0 || ^6.6.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0
|
||||
winston: ^3.0.0
|
||||
|
||||
node-abort-controller@3.1.1:
|
||||
resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==}
|
||||
|
||||
@@ -3438,10 +3662,17 @@ packages:
|
||||
once@1.4.0:
|
||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||
|
||||
one-time@1.0.0:
|
||||
resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==}
|
||||
|
||||
onetime@5.1.2:
|
||||
resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
opossum@9.0.0:
|
||||
resolution: {integrity: sha512-K76U0QkxOfUZamneQuzz+AP0fyfTJcCplZ2oZL93nxeupuJbN4s6uFNbmVCt4eWqqGqRnnowdFuBicJ1fLMVxw==}
|
||||
engines: {node: ^24 || ^22 || ^20}
|
||||
|
||||
optionator@0.9.4:
|
||||
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
@@ -3574,6 +3805,10 @@ packages:
|
||||
resolution: {integrity: sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==}
|
||||
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
|
||||
|
||||
prom-client@15.1.3:
|
||||
resolution: {integrity: sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==}
|
||||
engines: {node: ^16 || ^18 || >=20}
|
||||
|
||||
promise-coalesce@1.5.0:
|
||||
resolution: {integrity: sha512-cTJ30U+ur1LD7pMPyQxiKIwxjtAjLsyU7ivRhVWZrX9BNIXtf78pc37vSMc8Vikx7DVzEKNk2SEJ5KWUpSG2ig==}
|
||||
engines: {node: '>=16'}
|
||||
@@ -3663,6 +3898,10 @@ packages:
|
||||
resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
retry@0.13.1:
|
||||
resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==}
|
||||
engines: {node: '>= 4'}
|
||||
|
||||
reusify@1.1.0:
|
||||
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
|
||||
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
|
||||
@@ -3683,6 +3922,10 @@ packages:
|
||||
safe-buffer@5.2.1:
|
||||
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||
|
||||
safe-stable-stringify@2.5.0:
|
||||
resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
safer-buffer@2.1.2:
|
||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||
|
||||
@@ -3807,6 +4050,9 @@ packages:
|
||||
resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
stack-trace@0.0.10:
|
||||
resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==}
|
||||
|
||||
stack-utils@2.0.6:
|
||||
resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -3913,6 +4159,9 @@ packages:
|
||||
resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
tdigest@0.1.2:
|
||||
resolution: {integrity: sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==}
|
||||
|
||||
terser-webpack-plugin@5.3.14:
|
||||
resolution: {integrity: sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==}
|
||||
engines: {node: '>= 10.13.0'}
|
||||
@@ -3938,6 +4187,9 @@ packages:
|
||||
resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
text-hex@1.0.0:
|
||||
resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==}
|
||||
|
||||
tmpl@1.0.5:
|
||||
resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==}
|
||||
|
||||
@@ -3957,6 +4209,10 @@ packages:
|
||||
resolution: {integrity: sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==}
|
||||
engines: {node: '>=14.16'}
|
||||
|
||||
triple-beam@1.4.1:
|
||||
resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==}
|
||||
engines: {node: '>= 14.0.0'}
|
||||
|
||||
ts-api-utils@2.1.0:
|
||||
resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==}
|
||||
engines: {node: '>=18.12'}
|
||||
@@ -4030,6 +4286,10 @@ packages:
|
||||
resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
type-fest@0.20.2:
|
||||
resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
type-fest@0.21.3:
|
||||
resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -4235,6 +4495,18 @@ packages:
|
||||
engines: {node: '>= 8'}
|
||||
hasBin: true
|
||||
|
||||
widest-line@3.1.0:
|
||||
resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
winston-transport@4.9.0:
|
||||
resolution: {integrity: sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
|
||||
winston@3.18.3:
|
||||
resolution: {integrity: sha512-NoBZauFNNWENgsnC9YpgyYwOVrl2m58PpQ8lNHjV3kosGs7KJ7Npk9pCUE+WJlawVSe8mykWDKWFSVfs3QO9ww==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
|
||||
word-wrap@1.2.5:
|
||||
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -4968,10 +5240,18 @@ snapshots:
|
||||
'@colors/colors@1.5.0':
|
||||
optional: true
|
||||
|
||||
'@colors/colors@1.6.0': {}
|
||||
|
||||
'@cspotcode/source-map-support@0.8.1':
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.9
|
||||
|
||||
'@dabh/diagnostics@2.0.8':
|
||||
dependencies:
|
||||
'@so-ric/colorspace': 1.1.6
|
||||
enabled: 2.0.0
|
||||
kuler: 2.0.0
|
||||
|
||||
'@elastic/elasticsearch@8.19.1':
|
||||
dependencies:
|
||||
'@elastic/transport': 8.10.0
|
||||
@@ -5489,6 +5769,36 @@ snapshots:
|
||||
'@tybys/wasm-util': 0.10.1
|
||||
optional: true
|
||||
|
||||
'@nestjs-modules/ioredis@2.0.2(@nestjs/axios@4.0.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.2)(rxjs@7.8.2))(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@nestjs/typeorm@11.0.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))))(ioredis@5.8.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))':
|
||||
dependencies:
|
||||
'@nestjs/common': 11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
'@nestjs/core': 11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.9)(@nestjs/websockets@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
ioredis: 5.8.2
|
||||
optionalDependencies:
|
||||
'@nestjs/terminus': 10.2.0(@nestjs/axios@4.0.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.2)(rxjs@7.8.2))(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@nestjs/typeorm@11.0.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))))(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))
|
||||
transitivePeerDependencies:
|
||||
- '@grpc/grpc-js'
|
||||
- '@grpc/proto-loader'
|
||||
- '@mikro-orm/core'
|
||||
- '@mikro-orm/nestjs'
|
||||
- '@nestjs/axios'
|
||||
- '@nestjs/microservices'
|
||||
- '@nestjs/mongoose'
|
||||
- '@nestjs/sequelize'
|
||||
- '@nestjs/typeorm'
|
||||
- '@prisma/client'
|
||||
- mongoose
|
||||
- reflect-metadata
|
||||
- rxjs
|
||||
- sequelize
|
||||
- typeorm
|
||||
|
||||
'@nestjs/axios@4.0.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.2)(rxjs@7.8.2)':
|
||||
dependencies:
|
||||
'@nestjs/common': 11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
axios: 1.13.2
|
||||
rxjs: 7.8.2
|
||||
|
||||
'@nestjs/bull-shared@11.0.4(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)':
|
||||
dependencies:
|
||||
'@nestjs/common': 11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
@@ -5656,6 +5966,33 @@ snapshots:
|
||||
class-transformer: 0.5.1
|
||||
class-validator: 0.14.2
|
||||
|
||||
'@nestjs/terminus@10.2.0(@nestjs/axios@4.0.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.2)(rxjs@7.8.2))(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@nestjs/typeorm@11.0.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))))(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))':
|
||||
dependencies:
|
||||
'@nestjs/common': 11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
'@nestjs/core': 11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.9)(@nestjs/websockets@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
boxen: 5.1.2
|
||||
check-disk-space: 3.4.0
|
||||
reflect-metadata: 0.2.2
|
||||
rxjs: 7.8.2
|
||||
optionalDependencies:
|
||||
'@nestjs/axios': 4.0.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.2)(rxjs@7.8.2)
|
||||
'@nestjs/typeorm': 11.0.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))
|
||||
typeorm: 0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
|
||||
optional: true
|
||||
|
||||
'@nestjs/terminus@11.0.0(@nestjs/axios@4.0.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.2)(rxjs@7.8.2))(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@nestjs/typeorm@11.0.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))))(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))':
|
||||
dependencies:
|
||||
'@nestjs/common': 11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
'@nestjs/core': 11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.9)(@nestjs/websockets@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
boxen: 5.1.2
|
||||
check-disk-space: 3.4.0
|
||||
reflect-metadata: 0.2.2
|
||||
rxjs: 7.8.2
|
||||
optionalDependencies:
|
||||
'@nestjs/axios': 4.0.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.2)(rxjs@7.8.2)
|
||||
'@nestjs/typeorm': 11.0.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))
|
||||
typeorm: 0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
|
||||
|
||||
'@nestjs/testing@11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@nestjs/platform-express@11.1.9)':
|
||||
dependencies:
|
||||
'@nestjs/common': 11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
@@ -6038,6 +6375,11 @@ snapshots:
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@so-ric/colorspace@1.1.6':
|
||||
dependencies:
|
||||
color: 5.0.3
|
||||
text-hex: 1.0.0
|
||||
|
||||
'@socket.io/component-emitter@3.1.2': {}
|
||||
|
||||
'@sqltools/formatter@1.2.5': {}
|
||||
@@ -6071,6 +6413,10 @@ snapshots:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@types/async-retry@1.4.9':
|
||||
dependencies:
|
||||
'@types/retry': 0.12.5
|
||||
|
||||
'@types/babel__core@7.20.5':
|
||||
dependencies:
|
||||
'@babel/parser': 7.28.5
|
||||
@@ -6210,6 +6556,10 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- aws-crt
|
||||
|
||||
'@types/opossum@8.1.9':
|
||||
dependencies:
|
||||
'@types/node': 22.19.1
|
||||
|
||||
'@types/passport-jwt@4.0.1':
|
||||
dependencies:
|
||||
'@types/jsonwebtoken': 9.0.10
|
||||
@@ -6228,6 +6578,8 @@ snapshots:
|
||||
|
||||
'@types/range-parser@1.2.7': {}
|
||||
|
||||
'@types/retry@0.12.5': {}
|
||||
|
||||
'@types/send@0.17.6':
|
||||
dependencies:
|
||||
'@types/mime': 1.3.5
|
||||
@@ -6257,6 +6609,8 @@ snapshots:
|
||||
'@types/methods': 1.1.4
|
||||
'@types/superagent': 8.1.9
|
||||
|
||||
'@types/triple-beam@1.3.5': {}
|
||||
|
||||
'@types/uuid@11.0.0':
|
||||
dependencies:
|
||||
uuid: 13.0.0
|
||||
@@ -6574,6 +6928,10 @@ snapshots:
|
||||
json-schema-traverse: 1.0.0
|
||||
require-from-string: 2.0.2
|
||||
|
||||
ansi-align@3.0.1:
|
||||
dependencies:
|
||||
string-width: 4.2.3
|
||||
|
||||
ansi-colors@4.1.3: {}
|
||||
|
||||
ansi-escapes@4.3.2:
|
||||
@@ -6633,6 +6991,12 @@ snapshots:
|
||||
|
||||
asap@2.0.6: {}
|
||||
|
||||
async-retry@1.3.3:
|
||||
dependencies:
|
||||
retry: 0.13.1
|
||||
|
||||
async@3.2.6: {}
|
||||
|
||||
asynckit@0.4.0: {}
|
||||
|
||||
available-typed-arrays@1.0.7:
|
||||
@@ -6714,6 +7078,8 @@ snapshots:
|
||||
node-addon-api: 8.5.0
|
||||
node-gyp-build: 4.8.4
|
||||
|
||||
bintrees@1.0.2: {}
|
||||
|
||||
bl@4.1.0:
|
||||
dependencies:
|
||||
buffer: 5.7.1
|
||||
@@ -6736,6 +7102,17 @@ snapshots:
|
||||
|
||||
bowser@2.12.1: {}
|
||||
|
||||
boxen@5.1.2:
|
||||
dependencies:
|
||||
ansi-align: 3.0.1
|
||||
camelcase: 6.3.0
|
||||
chalk: 4.1.2
|
||||
cli-boxes: 2.2.1
|
||||
string-width: 4.2.3
|
||||
type-fest: 0.20.2
|
||||
widest-line: 3.1.0
|
||||
wrap-ansi: 7.0.0
|
||||
|
||||
brace-expansion@1.1.12:
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
@@ -6858,6 +7235,8 @@ snapshots:
|
||||
|
||||
chardet@2.1.1: {}
|
||||
|
||||
check-disk-space@3.4.0: {}
|
||||
|
||||
chokidar@4.0.3:
|
||||
dependencies:
|
||||
readdirp: 4.1.2
|
||||
@@ -6876,6 +7255,8 @@ snapshots:
|
||||
libphonenumber-js: 1.12.27
|
||||
validator: 13.15.23
|
||||
|
||||
cli-boxes@2.2.1: {}
|
||||
|
||||
cli-cursor@3.1.0:
|
||||
dependencies:
|
||||
restore-cursor: 3.1.0
|
||||
@@ -6908,8 +7289,23 @@ snapshots:
|
||||
dependencies:
|
||||
color-name: 1.1.4
|
||||
|
||||
color-convert@3.1.3:
|
||||
dependencies:
|
||||
color-name: 2.1.0
|
||||
|
||||
color-name@1.1.4: {}
|
||||
|
||||
color-name@2.1.0: {}
|
||||
|
||||
color-string@2.1.4:
|
||||
dependencies:
|
||||
color-name: 2.1.0
|
||||
|
||||
color@5.0.3:
|
||||
dependencies:
|
||||
color-convert: 3.1.3
|
||||
color-string: 2.1.4
|
||||
|
||||
combined-stream@1.0.8:
|
||||
dependencies:
|
||||
delayed-stream: 1.0.0
|
||||
@@ -7070,6 +7466,8 @@ snapshots:
|
||||
|
||||
emoji-regex@9.2.2: {}
|
||||
|
||||
enabled@2.0.0: {}
|
||||
|
||||
encodeurl@2.0.0: {}
|
||||
|
||||
engine.io-parser@5.2.3: {}
|
||||
@@ -7306,6 +7704,8 @@ snapshots:
|
||||
dependencies:
|
||||
bser: 2.1.1
|
||||
|
||||
fecha@4.2.3: {}
|
||||
|
||||
fflate@0.8.2: {}
|
||||
|
||||
file-entry-cache@8.0.0:
|
||||
@@ -7357,6 +7757,8 @@ snapshots:
|
||||
|
||||
flatted@3.3.3: {}
|
||||
|
||||
fn.name@1.1.0: {}
|
||||
|
||||
follow-redirects@1.15.11: {}
|
||||
|
||||
for-each@0.3.5:
|
||||
@@ -8071,6 +8473,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@keyv/serialize': 1.1.1
|
||||
|
||||
kuler@2.0.0: {}
|
||||
|
||||
leven@3.1.0: {}
|
||||
|
||||
levn@0.4.1:
|
||||
@@ -8127,6 +8531,15 @@ snapshots:
|
||||
chalk: 4.1.2
|
||||
is-unicode-supported: 0.1.0
|
||||
|
||||
logform@2.7.0:
|
||||
dependencies:
|
||||
'@colors/colors': 1.6.0
|
||||
'@types/triple-beam': 1.3.5
|
||||
fecha: 4.2.3
|
||||
ms: 2.1.3
|
||||
safe-stable-stringify: 2.5.0
|
||||
triple-beam: 1.4.1
|
||||
|
||||
long@5.3.2: {}
|
||||
|
||||
lru-cache@10.4.3: {}
|
||||
@@ -8272,6 +8685,12 @@ snapshots:
|
||||
|
||||
neo-async@2.6.2: {}
|
||||
|
||||
nest-winston@1.10.2(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(winston@3.18.3):
|
||||
dependencies:
|
||||
'@nestjs/common': 11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
fast-safe-stringify: 2.1.1
|
||||
winston: 3.18.3
|
||||
|
||||
node-abort-controller@3.1.1: {}
|
||||
|
||||
node-addon-api@8.5.0: {}
|
||||
@@ -8313,10 +8732,16 @@ snapshots:
|
||||
dependencies:
|
||||
wrappy: 1.0.2
|
||||
|
||||
one-time@1.0.0:
|
||||
dependencies:
|
||||
fn.name: 1.1.0
|
||||
|
||||
onetime@5.1.2:
|
||||
dependencies:
|
||||
mimic-fn: 2.1.0
|
||||
|
||||
opossum@9.0.0: {}
|
||||
|
||||
optionator@0.9.4:
|
||||
dependencies:
|
||||
deep-is: 0.1.4
|
||||
@@ -8438,6 +8863,11 @@ snapshots:
|
||||
ansi-styles: 5.2.0
|
||||
react-is: 18.3.1
|
||||
|
||||
prom-client@15.1.3:
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
tdigest: 0.1.2
|
||||
|
||||
promise-coalesce@1.5.0: {}
|
||||
|
||||
proxy-addr@2.0.7:
|
||||
@@ -8518,6 +8948,8 @@ snapshots:
|
||||
onetime: 5.1.2
|
||||
signal-exit: 3.0.7
|
||||
|
||||
retry@0.13.1: {}
|
||||
|
||||
reusify@1.1.0: {}
|
||||
|
||||
router@2.2.0:
|
||||
@@ -8544,6 +8976,8 @@ snapshots:
|
||||
|
||||
safe-buffer@5.2.1: {}
|
||||
|
||||
safe-stable-stringify@2.5.0: {}
|
||||
|
||||
safer-buffer@2.1.2: {}
|
||||
|
||||
schema-utils@3.3.0:
|
||||
@@ -8705,6 +9139,8 @@ snapshots:
|
||||
|
||||
sqlstring@2.3.3: {}
|
||||
|
||||
stack-trace@0.0.10: {}
|
||||
|
||||
stack-utils@2.0.6:
|
||||
dependencies:
|
||||
escape-string-regexp: 2.0.0
|
||||
@@ -8811,6 +9247,10 @@ snapshots:
|
||||
|
||||
tapable@2.3.0: {}
|
||||
|
||||
tdigest@0.1.2:
|
||||
dependencies:
|
||||
bintrees: 1.0.2
|
||||
|
||||
terser-webpack-plugin@5.3.14(webpack@5.100.2):
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
@@ -8833,6 +9273,8 @@ snapshots:
|
||||
glob: 7.2.3
|
||||
minimatch: 3.1.2
|
||||
|
||||
text-hex@1.0.0: {}
|
||||
|
||||
tmpl@1.0.5: {}
|
||||
|
||||
to-buffer@1.2.2:
|
||||
@@ -8853,6 +9295,8 @@ snapshots:
|
||||
'@tokenizer/token': 0.3.0
|
||||
ieee754: 1.2.1
|
||||
|
||||
triple-beam@1.4.1: {}
|
||||
|
||||
ts-api-utils@2.1.0(typescript@5.9.3):
|
||||
dependencies:
|
||||
typescript: 5.9.3
|
||||
@@ -8926,6 +9370,8 @@ snapshots:
|
||||
|
||||
type-detect@4.0.8: {}
|
||||
|
||||
type-fest@0.20.2: {}
|
||||
|
||||
type-fest@0.21.3: {}
|
||||
|
||||
type-fest@4.41.0: {}
|
||||
@@ -9126,6 +9572,30 @@ snapshots:
|
||||
dependencies:
|
||||
isexe: 2.0.0
|
||||
|
||||
widest-line@3.1.0:
|
||||
dependencies:
|
||||
string-width: 4.2.3
|
||||
|
||||
winston-transport@4.9.0:
|
||||
dependencies:
|
||||
logform: 2.7.0
|
||||
readable-stream: 3.6.2
|
||||
triple-beam: 1.4.1
|
||||
|
||||
winston@3.18.3:
|
||||
dependencies:
|
||||
'@colors/colors': 1.6.0
|
||||
'@dabh/diagnostics': 2.0.8
|
||||
async: 3.2.6
|
||||
is-stream: 2.0.1
|
||||
logform: 2.7.0
|
||||
one-time: 1.0.0
|
||||
readable-stream: 3.6.2
|
||||
safe-stable-stringify: 2.5.0
|
||||
stack-trace: 0.0.10
|
||||
triple-beam: 1.4.1
|
||||
winston-transport: 4.9.0
|
||||
|
||||
word-wrap@1.2.5: {}
|
||||
|
||||
wordwrap@1.0.0: {}
|
||||
|
||||
@@ -42,6 +42,7 @@ import { MonitoringModule } from './modules/monitoring/monitoring.module';
|
||||
import { ResilienceModule } from './common/resilience/resilience.module'; // ✅ Import
|
||||
// ... imports
|
||||
import { SearchModule } from './modules/search/search.module'; // ✅ Import
|
||||
import { RedisModule } from '@nestjs-modules/ioredis'; // [NEW]
|
||||
@Module({
|
||||
imports: [
|
||||
// 1. Setup Config Module พร้อม Validation
|
||||
@@ -113,7 +114,18 @@ import { SearchModule } from './modules/search/search.module'; // ✅ Import
|
||||
},
|
||||
}),
|
||||
}),
|
||||
|
||||
// [NEW] Setup Redis Module (สำหรับ InjectRedis)
|
||||
RedisModule.forRootAsync({
|
||||
imports: [ConfigModule],
|
||||
useFactory: (configService: ConfigService) => ({
|
||||
type: 'single',
|
||||
url: `redis://${configService.get('REDIS_HOST')}:${configService.get('REDIS_PORT')}`,
|
||||
options: {
|
||||
password: configService.get('REDIS_PASSWORD'),
|
||||
},
|
||||
}),
|
||||
inject: [ConfigService],
|
||||
}),
|
||||
// 📊 Register Monitoring Module (Health & Metrics) [Req 6.10]
|
||||
MonitoringModule,
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
CreateDateColumn,
|
||||
ManyToOne,
|
||||
JoinColumn,
|
||||
PrimaryColumn, // ✅ [Fix] เพิ่ม Import นี้
|
||||
} from 'typeorm';
|
||||
import { User } from '../../modules/user/entities/user.entity';
|
||||
|
||||
@@ -46,7 +47,9 @@ export class AuditLog {
|
||||
@Column({ name: 'user_agent', length: 255, nullable: true })
|
||||
userAgent?: string;
|
||||
|
||||
// ✅ [Fix] รวม Decorator ไว้ที่นี่ที่เดียว
|
||||
@CreateDateColumn({ name: 'created_at' })
|
||||
@PrimaryColumn() // เพื่อบอกว่าเป็น Composite PK คู่กับ auditId
|
||||
createdAt!: Date;
|
||||
|
||||
// Relations
|
||||
|
||||
75
backend/src/database/migrations/01_init_partitioning.sql
Normal file
75
backend/src/database/migrations/01_init_partitioning.sql
Normal file
@@ -0,0 +1,75 @@
|
||||
-- ============================================================
|
||||
-- Database Partitioning Script for LCBP3-DMS (Fixed #1075)
|
||||
-- Target Tables: audit_logs, notifications
|
||||
-- Strategy: Range Partitioning by YEAR(created_at)
|
||||
-- ============================================================
|
||||
-- ------------------------------------------------------------
|
||||
-- 1. Audit Logs Partitioning
|
||||
-- ------------------------------------------------------------
|
||||
-- Step 1: เอา AUTO_INCREMENT ออกก่อน (เพื่อไม่ให้ติด Error 1075 ตอนลบ PK)
|
||||
ALTER TABLE audit_logs
|
||||
MODIFY audit_id BIGINT NOT NULL;
|
||||
-- Step 2: ลบ Primary Key เดิม
|
||||
ALTER TABLE audit_logs DROP PRIMARY KEY;
|
||||
-- Step 3: สร้าง Primary Key ใหม่ (รวม created_at เพื่อทำ Partition)
|
||||
ALTER TABLE audit_logs
|
||||
ADD PRIMARY KEY (audit_id, created_at);
|
||||
-- Step 4: ใส่ AUTO_INCREMENT กลับเข้าไป
|
||||
ALTER TABLE audit_logs
|
||||
MODIFY audit_id BIGINT NOT NULL AUTO_INCREMENT;
|
||||
-- Step 5: สร้าง Partition
|
||||
ALTER TABLE audit_logs PARTITION BY RANGE (YEAR(created_at)) (
|
||||
PARTITION p_old
|
||||
VALUES LESS THAN (2024),
|
||||
PARTITION p2024
|
||||
VALUES LESS THAN (2025),
|
||||
PARTITION p2025
|
||||
VALUES LESS THAN (2026),
|
||||
PARTITION p2026
|
||||
VALUES LESS THAN (2027),
|
||||
PARTITION p2027
|
||||
VALUES LESS THAN (2028),
|
||||
PARTITION p2028
|
||||
VALUES LESS THAN (2029),
|
||||
PARTITION p2029
|
||||
VALUES LESS THAN (2030),
|
||||
PARTITION p2030
|
||||
VALUES LESS THAN (2031),
|
||||
PARTITION p_future
|
||||
VALUES LESS THAN MAXVALUE
|
||||
);
|
||||
-- ------------------------------------------------------------
|
||||
-- 2. Notifications Partitioning
|
||||
-- ------------------------------------------------------------
|
||||
-- Step 1: เอา AUTO_INCREMENT ออกก่อน
|
||||
ALTER TABLE notifications
|
||||
MODIFY id INT NOT NULL;
|
||||
-- Step 2: ลบ Primary Key เดิม
|
||||
ALTER TABLE notifications DROP PRIMARY KEY;
|
||||
-- Step 3: สร้าง Primary Key ใหม่
|
||||
ALTER TABLE notifications
|
||||
ADD PRIMARY KEY (id, created_at);
|
||||
-- Step 4: ใส่ AUTO_INCREMENT กลับเข้าไป
|
||||
ALTER TABLE notifications
|
||||
MODIFY id INT NOT NULL AUTO_INCREMENT;
|
||||
-- Step 5: สร้าง Partition
|
||||
ALTER TABLE notifications PARTITION BY RANGE (YEAR(created_at)) (
|
||||
PARTITION p_old
|
||||
VALUES LESS THAN (2024),
|
||||
PARTITION p2024
|
||||
VALUES LESS THAN (2025),
|
||||
PARTITION p2025
|
||||
VALUES LESS THAN (2026),
|
||||
PARTITION p2026
|
||||
VALUES LESS THAN (2027),
|
||||
PARTITION p2027
|
||||
VALUES LESS THAN (2028),
|
||||
PARTITION p2028
|
||||
VALUES LESS THAN (2029),
|
||||
PARTITION p2029
|
||||
VALUES LESS THAN (2030),
|
||||
PARTITION p2030
|
||||
VALUES LESS THAN (2031),
|
||||
PARTITION p_future
|
||||
VALUES LESS THAN MAXVALUE
|
||||
);
|
||||
82
backend/src/database/seeds/workflow-definitions.seed.ts
Normal file
82
backend/src/database/seeds/workflow-definitions.seed.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
// src/database/seeds/workflow-definitions.seed.ts
|
||||
|
||||
import { DataSource } from 'typeorm';
|
||||
import { WorkflowDefinition } from '../../modules/workflow-engine/entities/workflow-definition.entity';
|
||||
import { WorkflowDslService } from '../../modules/workflow-engine/workflow-dsl.service';
|
||||
|
||||
export const seedWorkflowDefinitions = async (dataSource: DataSource) => {
|
||||
const repo = dataSource.getRepository(WorkflowDefinition);
|
||||
const dslService = new WorkflowDslService();
|
||||
|
||||
// 1. RFA Workflow (Standard)
|
||||
const rfaDsl = {
|
||||
workflow: 'RFA',
|
||||
version: 1,
|
||||
states: [
|
||||
{
|
||||
name: 'DRAFT',
|
||||
initial: true,
|
||||
on: { SUBMIT: { to: 'IN_REVIEW', requirements: [{ role: 'Editor' }] } },
|
||||
},
|
||||
{
|
||||
name: 'IN_REVIEW',
|
||||
on: {
|
||||
APPROVE: {
|
||||
to: 'APPROVED',
|
||||
requirements: [{ role: 'Contract Admin' }],
|
||||
},
|
||||
REJECT: {
|
||||
to: 'REJECTED',
|
||||
requirements: [{ role: 'Contract Admin' }],
|
||||
},
|
||||
COMMENT: { to: 'DRAFT', requirements: [{ role: 'Contract Admin' }] }, // ส่งกลับแก้ไข
|
||||
},
|
||||
},
|
||||
{ name: 'APPROVED', terminal: true },
|
||||
{ name: 'REJECTED', terminal: true },
|
||||
],
|
||||
};
|
||||
|
||||
// 2. Circulation Workflow
|
||||
const circulationDsl = {
|
||||
workflow: 'CIRCULATION',
|
||||
version: 1,
|
||||
states: [
|
||||
{
|
||||
name: 'OPEN',
|
||||
initial: true,
|
||||
on: { SEND: { to: 'IN_REVIEW' } },
|
||||
},
|
||||
{
|
||||
name: 'IN_REVIEW',
|
||||
on: {
|
||||
COMPLETE: { to: 'COMPLETED' }, // เมื่อทุกคนตอบครบ
|
||||
CANCEL: { to: 'CANCELLED' },
|
||||
},
|
||||
},
|
||||
{ name: 'COMPLETED', terminal: true },
|
||||
{ name: 'CANCELLED', terminal: true },
|
||||
],
|
||||
};
|
||||
|
||||
const workflows = [rfaDsl, circulationDsl];
|
||||
|
||||
for (const dsl of workflows) {
|
||||
const exists = await repo.findOne({
|
||||
where: { workflow_code: dsl.workflow, version: dsl.version },
|
||||
});
|
||||
if (!exists) {
|
||||
const compiled = dslService.compile(dsl);
|
||||
await repo.save(
|
||||
repo.create({
|
||||
workflow_code: dsl.workflow,
|
||||
version: dsl.version,
|
||||
dsl: dsl,
|
||||
compiled: compiled,
|
||||
is_active: true,
|
||||
}),
|
||||
);
|
||||
console.log(`✅ Seeded Workflow: ${dsl.workflow} v${dsl.version}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,5 +1,3 @@
|
||||
// File: src/modules/master/dto/create-tag.dto.ts
|
||||
|
||||
import { IsString, IsNotEmpty, IsOptional } from 'class-validator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
@@ -7,12 +5,9 @@ export class CreateTagDto {
|
||||
@ApiProperty({ example: 'URGENT', description: 'ชื่อ Tag' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
tag_name: string;
|
||||
tag_name!: string; // เพิ่ม !
|
||||
|
||||
@ApiProperty({
|
||||
example: 'เอกสารด่วนต้องดำเนินการทันที',
|
||||
description: 'คำอธิบาย',
|
||||
})
|
||||
@ApiProperty({ example: 'คำอธิบาย', description: 'คำอธิบาย' })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
description?: string;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// File: src/modules/master/entities/tag.entity.ts
|
||||
|
||||
import {
|
||||
Entity,
|
||||
Column,
|
||||
@@ -11,17 +9,17 @@ import {
|
||||
@Entity('tags')
|
||||
export class Tag {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
id!: number; // เพิ่ม !
|
||||
|
||||
@Column({ length: 100, unique: true, comment: 'ชื่อ Tag' })
|
||||
tag_name: string;
|
||||
@Column({ length: 100, unique: true })
|
||||
tag_name!: string; // เพิ่ม !
|
||||
|
||||
@Column({ type: 'text', nullable: true, comment: 'คำอธิบายแท็ก' })
|
||||
description: string;
|
||||
@Column({ type: 'text', nullable: true })
|
||||
description!: string; // เพิ่ม !
|
||||
|
||||
@CreateDateColumn()
|
||||
created_at: Date;
|
||||
created_at!: Date; // เพิ่ม !
|
||||
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date;
|
||||
updated_at!: Date; // เพิ่ม !
|
||||
}
|
||||
|
||||
@@ -49,15 +49,15 @@ export class MasterService {
|
||||
|
||||
async findAllCorrespondenceTypes() {
|
||||
return this.corrTypeRepo.find({
|
||||
where: { is_active: true },
|
||||
order: { sort_order: 'ASC' },
|
||||
where: { isActive: true }, // ✅ แก้เป็น camelCase
|
||||
order: { sortOrder: 'ASC' }, // ✅ แก้เป็น camelCase
|
||||
});
|
||||
}
|
||||
|
||||
async findAllCorrespondenceStatuses() {
|
||||
return this.corrStatusRepo.find({
|
||||
where: { is_active: true },
|
||||
order: { sort_order: 'ASC' },
|
||||
where: { isActive: true }, // ✅ แก้เป็น camelCase
|
||||
order: { sortOrder: 'ASC' }, // ✅ แก้เป็น camelCase
|
||||
});
|
||||
}
|
||||
|
||||
@@ -67,22 +67,22 @@ export class MasterService {
|
||||
|
||||
async findAllRfaTypes() {
|
||||
return this.rfaTypeRepo.find({
|
||||
where: { is_active: true },
|
||||
order: { sort_order: 'ASC' },
|
||||
where: { isActive: true }, // ✅ แก้เป็น camelCase
|
||||
order: { sortOrder: 'ASC' }, // ✅ แก้เป็น camelCase
|
||||
});
|
||||
}
|
||||
|
||||
async findAllRfaStatuses() {
|
||||
return this.rfaStatusRepo.find({
|
||||
where: { is_active: true },
|
||||
order: { sort_order: 'ASC' },
|
||||
where: { isActive: true }, // ✅ แก้เป็น camelCase
|
||||
order: { sortOrder: 'ASC' }, // ✅ แก้เป็น camelCase
|
||||
});
|
||||
}
|
||||
|
||||
async findAllRfaApproveCodes() {
|
||||
return this.rfaApproveRepo.find({
|
||||
where: { is_active: true },
|
||||
order: { sort_order: 'ASC' },
|
||||
where: { isActive: true }, // ✅ แก้เป็น camelCase
|
||||
order: { sortOrder: 'ASC' }, // ✅ แก้เป็น camelCase
|
||||
});
|
||||
}
|
||||
|
||||
@@ -92,8 +92,8 @@ export class MasterService {
|
||||
|
||||
async findAllCirculationStatuses() {
|
||||
return this.circulationStatusRepo.find({
|
||||
where: { is_active: true },
|
||||
order: { sort_order: 'ASC' },
|
||||
where: { isActive: true }, // ✅ แก้เป็น camelCase
|
||||
order: { sortOrder: 'ASC' }, // ✅ แก้เป็น camelCase
|
||||
});
|
||||
}
|
||||
|
||||
@@ -101,9 +101,6 @@ export class MasterService {
|
||||
// 🏷️ Tag Management (CRUD)
|
||||
// =================================================================
|
||||
|
||||
/**
|
||||
* ค้นหา Tag ทั้งหมด พร้อมรองรับการ Search และ Pagination
|
||||
*/
|
||||
async findAllTags(query?: SearchTagDto) {
|
||||
const qb = this.tagRepo.createQueryBuilder('tag');
|
||||
|
||||
@@ -115,14 +112,12 @@ export class MasterService {
|
||||
|
||||
qb.orderBy('tag.tag_name', 'ASC');
|
||||
|
||||
// Pagination Logic
|
||||
if (query?.page && query?.limit) {
|
||||
const page = query.page;
|
||||
const limit = query.limit;
|
||||
qb.skip((page - 1) * limit).take(limit);
|
||||
}
|
||||
|
||||
// ถ้ามีการแบ่งหน้า ให้ส่งคืนทั้งข้อมูลและจำนวนทั้งหมด (count)
|
||||
if (query?.page && query?.limit) {
|
||||
const [items, total] = await qb.getManyAndCount();
|
||||
return {
|
||||
@@ -153,7 +148,7 @@ export class MasterService {
|
||||
}
|
||||
|
||||
async updateTag(id: number, dto: UpdateTagDto) {
|
||||
const tag = await this.findOneTag(id); // Reuse findOne for check
|
||||
const tag = await this.findOneTag(id);
|
||||
Object.assign(tag, dto);
|
||||
return this.tagRepo.save(tag);
|
||||
}
|
||||
|
||||
16
backend/src/modules/monitoring/dto/set-maintenance.dto.ts
Normal file
16
backend/src/modules/monitoring/dto/set-maintenance.dto.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { IsBoolean, IsOptional, IsString } from 'class-validator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class SetMaintenanceDto {
|
||||
@ApiProperty({ description: 'สถานะ Maintenance (true = เปิด, false = ปิด)' })
|
||||
@IsBoolean()
|
||||
enabled!: boolean; // ✅ เพิ่ม ! ตรงนี้
|
||||
|
||||
@ApiProperty({
|
||||
description: 'เหตุผลที่ปิดปรับปรุง (แสดงให้ User เห็น)',
|
||||
required: false,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
reason?: string; // Optional (?) ไม่ต้องใส่ !
|
||||
}
|
||||
30
backend/src/modules/monitoring/monitoring.controller.ts
Normal file
30
backend/src/modules/monitoring/monitoring.controller.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Controller, Get, Post, Body, UseGuards } from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { MonitoringService } from './monitoring.service';
|
||||
import { SetMaintenanceDto } from './dto/set-maintenance.dto';
|
||||
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
|
||||
import { RequirePermission } from '../../common/decorators/require-permission.decorator';
|
||||
import { BypassMaintenance } from '../../common/decorators/bypass-maintenance.decorator';
|
||||
|
||||
@ApiTags('System Monitoring')
|
||||
@Controller('monitoring')
|
||||
export class MonitoringController {
|
||||
constructor(private readonly monitoringService: MonitoringService) {}
|
||||
|
||||
@Get('maintenance')
|
||||
@ApiOperation({ summary: 'Check maintenance status (Public)' })
|
||||
@BypassMaintenance() // API นี้ต้องเรียกได้แม้ระบบปิดอยู่
|
||||
getMaintenanceStatus() {
|
||||
return this.monitoringService.getMaintenanceStatus();
|
||||
}
|
||||
|
||||
@Post('maintenance')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@RequirePermission('system.manage_all') // เฉพาะ Superadmin เท่านั้น
|
||||
@BypassMaintenance() // Admin ต้องยิงเปิด/ปิดได้แม้ระบบจะปิดอยู่
|
||||
@ApiOperation({ summary: 'Toggle Maintenance Mode (Admin Only)' })
|
||||
setMaintenanceMode(@Body() dto: SetMaintenanceDto) {
|
||||
return this.monitoringService.setMaintenanceMode(dto);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,34 @@
|
||||
// File: src/modules/monitoring/monitoring.module.ts
|
||||
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { TerminusModule } from '@nestjs/terminus';
|
||||
import { HttpModule } from '@nestjs/axios';
|
||||
import { APP_INTERCEPTOR } from '@nestjs/core';
|
||||
|
||||
// Existing Components
|
||||
import { HealthController } from './controllers/health.controller';
|
||||
import { MetricsService } from './services/metrics.service';
|
||||
import { PerformanceInterceptor } from '../../common/interceptors/performance.interceptor';
|
||||
|
||||
@Global() // ทำให้ Module นี้ใช้งานได้ทั่วทั้ง App โดยไม่ต้อง Import ซ้ำ
|
||||
// [NEW] Maintenance Mode Components
|
||||
import { MonitoringController } from './monitoring.controller';
|
||||
import { MonitoringService } from './monitoring.service';
|
||||
|
||||
@Global() // Module นี้เป็น Global (ดีแล้วครับ)
|
||||
@Module({
|
||||
imports: [TerminusModule, HttpModule],
|
||||
controllers: [HealthController],
|
||||
controllers: [
|
||||
HealthController, // ✅ ของเดิม: /health
|
||||
MonitoringController, // ✅ ของใหม่: /monitoring/maintenance
|
||||
],
|
||||
providers: [
|
||||
MetricsService,
|
||||
MetricsService, // ✅ ของเดิม
|
||||
MonitoringService, // ✅ ของใหม่ (Logic เปิด/ปิด Maintenance)
|
||||
{
|
||||
provide: APP_INTERCEPTOR, // Register Global Interceptor
|
||||
useClass: PerformanceInterceptor,
|
||||
provide: APP_INTERCEPTOR,
|
||||
useClass: PerformanceInterceptor, // ✅ ของเดิม (จับเวลา Response Time)
|
||||
},
|
||||
],
|
||||
exports: [MetricsService],
|
||||
exports: [MetricsService, MonitoringService],
|
||||
})
|
||||
export class MonitoringModule {}
|
||||
|
||||
44
backend/src/modules/monitoring/monitoring.service.ts
Normal file
44
backend/src/modules/monitoring/monitoring.service.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
// File: src/modules/monitoring/monitoring.service.ts
|
||||
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRedis } from '@nestjs-modules/ioredis';
|
||||
import Redis from 'ioredis';
|
||||
import { SetMaintenanceDto } from './dto/set-maintenance.dto';
|
||||
|
||||
@Injectable()
|
||||
export class MonitoringService {
|
||||
private readonly logger = new Logger(MonitoringService.name);
|
||||
private readonly MAINTENANCE_KEY = 'system:maintenance_mode';
|
||||
|
||||
constructor(@InjectRedis() private readonly redis: Redis) {}
|
||||
|
||||
/**
|
||||
* ตรวจสอบสถานะปัจจุบัน
|
||||
*/
|
||||
async getMaintenanceStatus() {
|
||||
const status = await this.redis.get(this.MAINTENANCE_KEY);
|
||||
return {
|
||||
isEnabled: status === 'true',
|
||||
message:
|
||||
status === 'true' ? 'System is under maintenance' : 'System is normal',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* ตั้งค่า Maintenance Mode
|
||||
*/
|
||||
async setMaintenanceMode(dto: SetMaintenanceDto) {
|
||||
if (dto.enabled) {
|
||||
await this.redis.set(this.MAINTENANCE_KEY, 'true');
|
||||
// เก็บเหตุผลไว้ใน Key อื่นก็ได้ถ้าต้องการ แต่เบื้องต้น Guard เช็คแค่ Key นี้
|
||||
this.logger.warn(
|
||||
`⚠️ SYSTEM ENTERED MAINTENANCE MODE: ${dto.reason || 'No reason provided'}`,
|
||||
);
|
||||
} else {
|
||||
await this.redis.del(this.MAINTENANCE_KEY);
|
||||
this.logger.log('✅ System exited maintenance mode');
|
||||
}
|
||||
|
||||
return this.getMaintenanceStatus();
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
CreateDateColumn,
|
||||
ManyToOne,
|
||||
JoinColumn,
|
||||
PrimaryColumn, // ✅ [Fix] เพิ่ม Import นี้
|
||||
} from 'typeorm';
|
||||
import { User } from '../../user/entities/user.entity';
|
||||
|
||||
@@ -44,7 +45,9 @@ export class Notification {
|
||||
@Column({ name: 'entity_id', nullable: true })
|
||||
entityId?: number;
|
||||
|
||||
// ✅ [Fix] รวม Decorator ไว้ที่นี่ที่เดียว (เป็นทั้ง CreateDate และ PrimaryColumn สำหรับ Partition)
|
||||
@CreateDateColumn({ name: 'created_at' })
|
||||
@PrimaryColumn()
|
||||
createdAt!: Date;
|
||||
|
||||
// --- Relations ---
|
||||
|
||||
@@ -1,26 +1,43 @@
|
||||
import { Processor, WorkerHost } from '@nestjs/bullmq';
|
||||
import { Job } from 'bullmq';
|
||||
// File: src/modules/notification/notification.processor.ts
|
||||
|
||||
import { Processor, WorkerHost, InjectQueue } from '@nestjs/bullmq';
|
||||
import { Job, Queue } from 'bullmq';
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { InjectRedis } from '@nestjs-modules/ioredis';
|
||||
import Redis from 'ioredis';
|
||||
import * as nodemailer from 'nodemailer';
|
||||
import axios from 'axios';
|
||||
|
||||
import { UserService } from '../user/user.service';
|
||||
|
||||
interface NotificationPayload {
|
||||
userId: number;
|
||||
title: string;
|
||||
message: string;
|
||||
link: string;
|
||||
type: 'EMAIL' | 'LINE' | 'SYSTEM';
|
||||
}
|
||||
|
||||
@Processor('notifications')
|
||||
export class NotificationProcessor extends WorkerHost {
|
||||
private readonly logger = new Logger(NotificationProcessor.name);
|
||||
private mailerTransport: nodemailer.Transporter;
|
||||
|
||||
// ค่าคงที่สำหรับ Digest (เช่น รอ 5 นาที)
|
||||
private readonly DIGEST_DELAY = 5 * 60 * 1000;
|
||||
|
||||
constructor(
|
||||
private configService: ConfigService,
|
||||
private userService: UserService,
|
||||
@InjectQueue('notifications') private notificationQueue: Queue,
|
||||
@InjectRedis() private readonly redis: Redis,
|
||||
) {
|
||||
super();
|
||||
// Setup Nodemailer
|
||||
this.mailerTransport = nodemailer.createTransport({
|
||||
host: this.configService.get('SMTP_HOST'),
|
||||
port: this.configService.get('SMTP_PORT'),
|
||||
port: Number(this.configService.get('SMTP_PORT')),
|
||||
secure: this.configService.get('SMTP_SECURE') === 'true',
|
||||
auth: {
|
||||
user: this.configService.get('SMTP_USER'),
|
||||
@@ -30,59 +47,196 @@ export class NotificationProcessor extends WorkerHost {
|
||||
}
|
||||
|
||||
async process(job: Job<any, any, string>): Promise<any> {
|
||||
this.logger.debug(`Processing job ${job.name} for user ${job.data.userId}`);
|
||||
this.logger.debug(`Processing job ${job.name} (ID: ${job.id})`);
|
||||
|
||||
switch (job.name) {
|
||||
case 'send-email':
|
||||
return this.handleSendEmail(job.data);
|
||||
case 'send-line':
|
||||
return this.handleSendLine(job.data);
|
||||
default:
|
||||
throw new Error(`Unknown job name: ${job.name}`);
|
||||
try {
|
||||
switch (job.name) {
|
||||
case 'dispatch-notification':
|
||||
// Job หลัก: ตัดสินใจว่าจะส่งเลย หรือจะเข้า Digest Queue
|
||||
return this.handleDispatch(job.data);
|
||||
|
||||
case 'process-digest':
|
||||
// Job รอง: ทำงานเมื่อครบเวลา Delay เพื่อส่งแบบรวม
|
||||
return this.handleProcessDigest(job.data.userId, job.data.type);
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown job name: ${job.name}`);
|
||||
}
|
||||
} catch (error) {
|
||||
// ✅ แก้ไขตรงนี้: Type Casting (error as Error)
|
||||
this.logger.error(
|
||||
`Failed to process job ${job.name}: ${(error as Error).message}`,
|
||||
(error as Error).stack,
|
||||
);
|
||||
throw error; // ให้ BullMQ จัดการ Retry
|
||||
}
|
||||
}
|
||||
|
||||
private async handleSendEmail(data: any) {
|
||||
const user = await this.userService.findOne(data.userId);
|
||||
if (!user || !user.email) {
|
||||
this.logger.warn(`User ${data.userId} has no email`);
|
||||
/**
|
||||
* ฟังก์ชันตัดสินใจ (Dispatcher)
|
||||
* ตรวจสอบ User Preferences และ Digest Mode
|
||||
*/
|
||||
private async handleDispatch(data: NotificationPayload) {
|
||||
// 1. ดึง User พร้อม Preferences
|
||||
const user: any = await this.userService.findOne(data.userId);
|
||||
|
||||
if (!user) {
|
||||
this.logger.warn(`User ${data.userId} not found, skipping notification.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const prefs = user.preferences || {
|
||||
notify_email: true,
|
||||
notify_line: true,
|
||||
digest_mode: false,
|
||||
};
|
||||
|
||||
// 2. ตรวจสอบว่า User ปิดรับการแจ้งเตือนหรือไม่
|
||||
if (data.type === 'EMAIL' && !prefs.notify_email) return;
|
||||
if (data.type === 'LINE' && !prefs.notify_line) return;
|
||||
|
||||
// 3. ตรวจสอบ Digest Mode
|
||||
if (prefs.digest_mode) {
|
||||
await this.addToDigest(data);
|
||||
} else {
|
||||
// ส่งทันที (Real-time)
|
||||
if (data.type === 'EMAIL') await this.sendEmailImmediate(user, data);
|
||||
if (data.type === 'LINE') await this.sendLineImmediate(user, data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* เพิ่มข้อความลงใน Redis List และตั้งเวลาส่ง (Delayed Job)
|
||||
*/
|
||||
private async addToDigest(data: NotificationPayload) {
|
||||
const key = `digest:${data.type}:${data.userId}`;
|
||||
|
||||
// 1. Push ข้อมูลลง Redis List
|
||||
await this.redis.rpush(key, JSON.stringify(data));
|
||||
|
||||
// 2. ตรวจสอบว่ามี "ตัวนับเวลาถอยหลัง" (Delayed Job) อยู่หรือยัง?
|
||||
const lockKey = `digest:lock:${data.type}:${data.userId}`;
|
||||
const isLocked = await this.redis.get(lockKey);
|
||||
|
||||
if (!isLocked) {
|
||||
// ถ้ายังไม่มี Job รออยู่ ให้สร้างใหม่
|
||||
await this.notificationQueue.add(
|
||||
'process-digest',
|
||||
{ userId: data.userId, type: data.type },
|
||||
{
|
||||
delay: this.DIGEST_DELAY,
|
||||
jobId: `digest-${data.type}-${data.userId}-${Date.now()}`,
|
||||
},
|
||||
);
|
||||
|
||||
// Set Lock ไว้ตามเวลา Delay เพื่อไม่ให้สร้าง Job ซ้ำ
|
||||
await this.redis.set(lockKey, '1', 'PX', this.DIGEST_DELAY);
|
||||
this.logger.log(
|
||||
`Scheduled digest for User ${data.userId} (${data.type}) in ${this.DIGEST_DELAY}ms`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ประมวลผล Digest (ส่งแบบรวม)
|
||||
*/
|
||||
private async handleProcessDigest(userId: number, type: 'EMAIL' | 'LINE') {
|
||||
const key = `digest:${type}:${userId}`;
|
||||
const lockKey = `digest:lock:${type}:${userId}`;
|
||||
|
||||
// 1. ดึงข้อความทั้งหมดจาก Redis และลบออกทันที
|
||||
const messagesRaw = await this.redis.lrange(key, 0, -1);
|
||||
await this.redis.del(key);
|
||||
await this.redis.del(lockKey); // Clear lock
|
||||
|
||||
if (!messagesRaw || messagesRaw.length === 0) return;
|
||||
|
||||
const messages: NotificationPayload[] = messagesRaw.map((m) =>
|
||||
JSON.parse(m),
|
||||
);
|
||||
const user = await this.userService.findOne(userId);
|
||||
|
||||
if (type === 'EMAIL') {
|
||||
await this.sendEmailDigest(user, messages);
|
||||
} else if (type === 'LINE') {
|
||||
await this.sendLineDigest(user, messages);
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// SENDERS (Immediate & Digest)
|
||||
// =====================================================
|
||||
|
||||
private async sendEmailImmediate(user: any, data: NotificationPayload) {
|
||||
if (!user.email) return;
|
||||
await this.mailerTransport.sendMail({
|
||||
from: '"LCBP3 DMS" <no-reply@np-dms.work>',
|
||||
to: user.email,
|
||||
subject: `[DMS] ${data.title}`,
|
||||
html: `
|
||||
<h3>${data.title}</h3>
|
||||
<p>${data.message}</p>
|
||||
<br/>
|
||||
<a href="${data.link}">คลิกเพื่อดูรายละเอียด</a>
|
||||
`,
|
||||
html: `<h3>${data.title}</h3><p>${data.message}</p><br/><a href="${data.link}">คลิกเพื่อดูรายละเอียด</a>`,
|
||||
});
|
||||
this.logger.log(`Email sent to ${user.email}`);
|
||||
}
|
||||
|
||||
private async handleSendLine(data: any) {
|
||||
const user = await this.userService.findOne(data.userId);
|
||||
// ตรวจสอบว่า User มี Line ID หรือไม่ (หรือใช้ Group Token ถ้าเป็นระบบรวม)
|
||||
// ในที่นี้สมมติว่าเรายิงเข้า n8n webhook เพื่อจัดการต่อ
|
||||
const n8nWebhookUrl = this.configService.get('N8N_LINE_WEBHOOK_URL');
|
||||
private async sendEmailDigest(user: any, messages: NotificationPayload[]) {
|
||||
if (!user.email) return;
|
||||
|
||||
if (!n8nWebhookUrl) {
|
||||
this.logger.warn('N8N_LINE_WEBHOOK_URL not configured');
|
||||
return;
|
||||
}
|
||||
// สร้าง HTML List
|
||||
const listItems = messages
|
||||
.map(
|
||||
(msg) =>
|
||||
`<li><strong>${msg.title}</strong>: ${msg.message} <a href="${msg.link}">[View]</a></li>`,
|
||||
)
|
||||
.join('');
|
||||
|
||||
await this.mailerTransport.sendMail({
|
||||
from: '"LCBP3 DMS" <no-reply@np-dms.work>',
|
||||
to: user.email,
|
||||
subject: `[DMS Summary] คุณมีการแจ้งเตือนใหม่ ${messages.length} รายการ`,
|
||||
html: `
|
||||
<h3>สรุปรายการแจ้งเตือน (Digest)</h3>
|
||||
<ul>${listItems}</ul>
|
||||
<p>คุณได้รับอีเมลนี้เพราะเปิดใช้งานโหมดสรุปรายการ</p>
|
||||
`,
|
||||
});
|
||||
this.logger.log(
|
||||
`Digest Email sent to ${user.email} (${messages.length} items)`,
|
||||
);
|
||||
}
|
||||
|
||||
private async sendLineImmediate(user: any, data: NotificationPayload) {
|
||||
const n8nWebhookUrl = this.configService.get('N8N_LINE_WEBHOOK_URL');
|
||||
if (!n8nWebhookUrl) return;
|
||||
|
||||
try {
|
||||
await axios.post(n8nWebhookUrl, {
|
||||
userId: user.user_id, // หรือ user.lineId ถ้ามี
|
||||
userId: user.user_id,
|
||||
message: `${data.title}\n${data.message}`,
|
||||
link: data.link,
|
||||
isDigest: false,
|
||||
});
|
||||
this.logger.log(`Line notification sent via n8n for user ${data.userId}`);
|
||||
} catch (error: any) {
|
||||
throw new Error(`Failed to send Line notification: ${error.message}`);
|
||||
} catch (error) {
|
||||
// ✅ แก้ไขตรงนี้ด้วย: Type Casting (error as Error)
|
||||
this.logger.error(`Line Error: ${(error as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async sendLineDigest(user: any, messages: NotificationPayload[]) {
|
||||
const n8nWebhookUrl = this.configService.get('N8N_LINE_WEBHOOK_URL');
|
||||
if (!n8nWebhookUrl) return;
|
||||
|
||||
const summary = messages.map((m, i) => `${i + 1}. ${m.title}`).join('\n');
|
||||
|
||||
try {
|
||||
await axios.post(n8nWebhookUrl, {
|
||||
userId: user.user_id,
|
||||
message: `สรุป ${messages.length} รายการใหม่:\n${summary}`,
|
||||
link: 'https://lcbp3.np-dms.work/notifications',
|
||||
isDigest: true,
|
||||
});
|
||||
} catch (error) {
|
||||
// ✅ แก้ไขตรงนี้ด้วย: Type Casting (error as Error)
|
||||
this.logger.error(`Line Digest Error: ${(error as Error).message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// File: src/modules/notification/notification.service.ts
|
||||
|
||||
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
|
||||
import { InjectQueue } from '@nestjs/bullmq';
|
||||
import { Queue } from 'bullmq';
|
||||
@@ -22,9 +23,9 @@ export interface NotificationJobData {
|
||||
title: string;
|
||||
message: string;
|
||||
type: 'EMAIL' | 'LINE' | 'SYSTEM'; // ช่องทางหลักที่ต้องการส่ง (Trigger Type)
|
||||
entityType?: string; // e.g., 'rfa', 'correspondence'
|
||||
entityId?: number; // e.g., rfa_id
|
||||
link?: string; // Deep link to frontend page
|
||||
entityType?: string;
|
||||
entityId?: number;
|
||||
link?: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
@@ -37,109 +38,57 @@ export class NotificationService {
|
||||
private notificationRepo: Repository<Notification>,
|
||||
@InjectRepository(User)
|
||||
private userRepo: Repository<User>,
|
||||
@InjectRepository(UserPreference)
|
||||
private userPrefRepo: Repository<UserPreference>,
|
||||
// ไม่ต้อง Inject UserPrefRepo แล้ว เพราะ Processor จะจัดการเอง
|
||||
private notificationGateway: NotificationGateway,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* ส่งการแจ้งเตือน (Centralized Notification Sender)
|
||||
* 1. บันทึก DB (System Log)
|
||||
* 2. ส่ง Real-time (WebSocket)
|
||||
* 3. ส่ง External (Email/Line) ผ่าน Queue ตาม User Preference
|
||||
*/
|
||||
async send(data: NotificationJobData): Promise<void> {
|
||||
try {
|
||||
// ---------------------------------------------------------
|
||||
// 1. สร้าง Entity และบันทึกลง DB (เพื่อให้มี History ในระบบ)
|
||||
// 1. สร้าง Entity และบันทึกลง DB (System Log)
|
||||
// ---------------------------------------------------------
|
||||
const notification = this.notificationRepo.create({
|
||||
userId: data.userId,
|
||||
title: data.title,
|
||||
message: data.message,
|
||||
notificationType: NotificationType.SYSTEM, // ใน DB เก็บเป็น SYSTEM เสมอเพื่อแสดงใน App
|
||||
notificationType: NotificationType.SYSTEM,
|
||||
entityType: data.entityType,
|
||||
entityId: data.entityId,
|
||||
isRead: false,
|
||||
// link: data.link // ถ้า Entity มี field link ให้ใส่ด้วย
|
||||
});
|
||||
|
||||
const savedNotification = await this.notificationRepo.save(notification);
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 2. Real-time Push (WebSocket) -> ส่งให้ User ทันทีถ้า Online
|
||||
// 2. Real-time Push (WebSocket)
|
||||
// ---------------------------------------------------------
|
||||
this.notificationGateway.sendToUser(data.userId, savedNotification);
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 3. ตรวจสอบ User Preferences เพื่อส่งช่องทางอื่น (Email/Line)
|
||||
// 3. Push Job ลง Redis BullMQ (Dispatch Logic)
|
||||
// เปลี่ยนชื่อ Job เป็น 'dispatch-notification' ตาม Processor
|
||||
// ---------------------------------------------------------
|
||||
const userPref = await this.userPrefRepo.findOne({
|
||||
where: { userId: data.userId },
|
||||
});
|
||||
|
||||
// ใช้ Nullish Coalescing Operator (??)
|
||||
// ถ้าไม่มีค่า (undefined/null) ให้ Default เป็น true
|
||||
const shouldSendEmail = userPref?.notifyEmail ?? true;
|
||||
const shouldSendLine = userPref?.notifyLine ?? true;
|
||||
|
||||
const jobs = [];
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 4. เตรียม Job สำหรับ Email Queue
|
||||
// เงื่อนไข: User เปิดรับ Email และ Noti นี้ไม่ได้บังคับส่งแค่ LINE
|
||||
// ---------------------------------------------------------
|
||||
if (shouldSendEmail && data.type !== 'LINE') {
|
||||
jobs.push({
|
||||
name: 'send-email',
|
||||
data: {
|
||||
...data,
|
||||
notificationId: savedNotification.id,
|
||||
target: 'EMAIL',
|
||||
await this.notificationQueue.add(
|
||||
'dispatch-notification',
|
||||
{
|
||||
...data,
|
||||
notificationId: savedNotification.id, // ส่ง ID ไปด้วยเผื่อใช้ Tracking
|
||||
},
|
||||
{
|
||||
attempts: 3,
|
||||
backoff: {
|
||||
type: 'exponential',
|
||||
delay: 5000,
|
||||
},
|
||||
opts: {
|
||||
attempts: 3, // ลองใหม่ 3 ครั้งถ้าล่ม (Resilience)
|
||||
backoff: {
|
||||
type: 'exponential',
|
||||
delay: 5000, // รอ 5s, 10s, 20s...
|
||||
},
|
||||
removeOnComplete: true, // ลบ Job เมื่อเสร็จ (ประหยัด Redis Memory)
|
||||
},
|
||||
});
|
||||
}
|
||||
removeOnComplete: true,
|
||||
},
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 5. เตรียม Job สำหรับ Line Queue
|
||||
// เงื่อนไข: User เปิดรับ Line และ Noti นี้ไม่ได้บังคับส่งแค่ EMAIL
|
||||
// ---------------------------------------------------------
|
||||
if (shouldSendLine && data.type !== 'EMAIL') {
|
||||
jobs.push({
|
||||
name: 'send-line',
|
||||
data: {
|
||||
...data,
|
||||
notificationId: savedNotification.id,
|
||||
target: 'LINE',
|
||||
},
|
||||
opts: {
|
||||
attempts: 3,
|
||||
backoff: { type: 'fixed', delay: 3000 },
|
||||
removeOnComplete: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 6. Push Jobs ลง Redis BullMQ
|
||||
// ---------------------------------------------------------
|
||||
if (jobs.length > 0) {
|
||||
await this.notificationQueue.addBulk(jobs);
|
||||
this.logger.debug(
|
||||
`Queued ${jobs.length} external notifications for user ${data.userId}`,
|
||||
);
|
||||
}
|
||||
this.logger.debug(`Dispatched notification job for user ${data.userId}`);
|
||||
} catch (error) {
|
||||
// Error Handling: ไม่ Throw เพื่อไม่ให้ Flow หลัก (เช่น การสร้างเอกสาร) พัง
|
||||
// แต่บันทึก Error ไว้ตรวจสอบ
|
||||
this.logger.error(
|
||||
`Failed to process notification for user ${data.userId}`,
|
||||
(error as Error).stack,
|
||||
@@ -147,9 +96,8 @@ export class NotificationService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ดึงรายการแจ้งเตือนของ User (สำหรับ Controller)
|
||||
*/
|
||||
// ... (ส่วน findAll, markAsRead, cleanupOldNotifications เหมือนเดิม ไม่ต้องแก้) ...
|
||||
|
||||
async findAll(userId: number, searchDto: SearchNotificationDto) {
|
||||
const { page = 1, limit = 20, isRead } = searchDto;
|
||||
const skip = (page - 1) * limit;
|
||||
@@ -161,14 +109,11 @@ export class NotificationService {
|
||||
.take(limit)
|
||||
.skip(skip);
|
||||
|
||||
// Filter by Read Status (ถ้ามีการส่งมา)
|
||||
if (isRead !== undefined) {
|
||||
queryBuilder.andWhere('notification.isRead = :isRead', { isRead });
|
||||
}
|
||||
|
||||
const [items, total] = await queryBuilder.getManyAndCount();
|
||||
|
||||
// นับจำนวนที่ยังไม่ได้อ่านทั้งหมด (เพื่อแสดง Badge ที่กระดิ่ง)
|
||||
const unreadCount = await this.notificationRepo.count({
|
||||
where: { userId, isRead: false },
|
||||
});
|
||||
@@ -185,9 +130,6 @@ export class NotificationService {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* อ่านแจ้งเตือน (Mark as Read)
|
||||
*/
|
||||
async markAsRead(id: number, userId: number): Promise<void> {
|
||||
const notification = await this.notificationRepo.findOne({
|
||||
where: { id, userId },
|
||||
@@ -200,15 +142,9 @@ export class NotificationService {
|
||||
if (!notification.isRead) {
|
||||
notification.isRead = true;
|
||||
await this.notificationRepo.save(notification);
|
||||
|
||||
// Update Unread Count via WebSocket (Optional)
|
||||
// this.notificationGateway.sendUnreadCount(userId, ...);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* อ่านทั้งหมด (Mark All as Read)
|
||||
*/
|
||||
async markAllAsRead(userId: number): Promise<void> {
|
||||
await this.notificationRepo.update(
|
||||
{ userId, isRead: false },
|
||||
@@ -216,10 +152,6 @@ export class NotificationService {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* ลบการแจ้งเตือนที่เก่าเกินกำหนด (ใช้กับ Cron Job Cleanup)
|
||||
* เก็บไว้ 90 วัน
|
||||
*/
|
||||
async cleanupOldNotifications(days: number = 90): Promise<number> {
|
||||
const dateLimit = new Date();
|
||||
dateLimit.setDate(dateLimit.getDate() - days);
|
||||
|
||||
@@ -64,6 +64,7 @@ export class UserService {
|
||||
async findOne(id: number): Promise<User> {
|
||||
const user = await this.usersRepository.findOne({
|
||||
where: { user_id: id },
|
||||
relations: ['preferences', 'roles'], // [IMPORTANT] ต้องโหลด preferences มาด้วย
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
|
||||
Reference in New Issue
Block a user