first commit

This commit is contained in:
Mamadou Khoussa [028918 DSI/DAC/DIF/DS] 2025-10-21 23:10:16 +00:00
parent cb7314e386
commit 300a5205df
27 changed files with 1546 additions and 69 deletions

View File

@ -17,9 +17,9 @@ export default tseslint.config(
...globals.node,
...globals.jest,
},
sourceType: 'commonjs',
sourceType: 'module', // Changé de 'commonjs' à 'module'
parserOptions: {
projectService: true,
project: './tsconfig.json', // Ajout du chemin vers tsconfig
tsconfigRootDir: import.meta.dirname,
},
},
@ -28,7 +28,13 @@ export default tseslint.config(
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-floating-promises': 'warn',
'@typescript-eslint/no-unsafe-argument': 'warn'
'@typescript-eslint/no-unsafe-argument': 'warn',
'prettier/prettier': ['error', {
singleQuote: true,
trailingComma: 'all',
printWidth: 100,
tabWidth: 2,
}],
},
},
);

708
package-lock.json generated
View File

@ -9,10 +9,18 @@
"version": "0.0.1",
"license": "UNLICENSED",
"dependencies": {
"@nestjs/bull": "^11.0.4",
"@nestjs/cache-manager": "^3.0.1",
"@nestjs/common": "^11.0.1",
"@nestjs/config": "^4.0.2",
"@nestjs/core": "^11.0.1",
"@nestjs/event-emitter": "^3.0.1",
"@nestjs/platform-express": "^11.0.1",
"@nestjs/schedule": "^6.0.1",
"@nestjs/swagger": "^11.2.1",
"@prisma/client": "^6.17.1",
"cache-manager-redis-store": "^3.0.1",
"class-validator": "^0.14.2",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1"
},
@ -713,6 +721,16 @@
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/@cacheable/utils": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.1.0.tgz",
"integrity": "sha512-ZdxfOiaarMqMj+H7qwlt5EBKWaeGihSYVHdQv5lUsbn8MJJOTW82OIwirQ39U5tMZkNvy3bQE+ryzC+xTAb9/g==",
"license": "MIT",
"peer": true,
"dependencies": {
"keyv": "^5.5.3"
}
},
"node_modules/@colors/colors": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
@ -1341,6 +1359,13 @@
}
}
},
"node_modules/@ioredis/commands": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.4.0.tgz",
"integrity": "sha512-aFT2yemJJo+TZCmieA7qnYGQooOS7QfNmYrzGtsYd3g9j5iDP8AimYYAesf79ohjbLG12XxC4nG5DyEnC88AsQ==",
"license": "MIT",
"peer": true
},
"node_modules/@isaacs/balanced-match": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
@ -2045,6 +2070,13 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@keyv/serialize": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz",
"integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==",
"license": "MIT",
"peer": true
},
"node_modules/@lukeed/csprng": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz",
@ -2054,6 +2086,96 @@
"node": ">=8"
}
},
"node_modules/@microsoft/tsdoc": {
"version": "0.15.1",
"resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.15.1.tgz",
"integrity": "sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==",
"license": "MIT"
},
"node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz",
"integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"peer": true
},
"node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz",
"integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"peer": true
},
"node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz",
"integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"peer": true
},
"node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz",
"integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"peer": true
},
"node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz",
"integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"peer": true
},
"node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz",
"integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"peer": true
},
"node_modules/@napi-rs/wasm-runtime": {
"version": "0.2.12",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
@ -2067,6 +2189,47 @@
"@tybys/wasm-util": "^0.10.0"
}
},
"node_modules/@nestjs/bull": {
"version": "11.0.4",
"resolved": "https://registry.npmjs.org/@nestjs/bull/-/bull-11.0.4.tgz",
"integrity": "sha512-QVz2PR/rJF/isy7otVnMTSqLf/O71p9Ka7lBZt9Gm+NQFv8fcH2L11GL7TA0whyCcw/kAX5iRepUXz/wed4JoA==",
"license": "MIT",
"dependencies": {
"@nestjs/bull-shared": "^11.0.4",
"tslib": "2.8.1"
},
"peerDependencies": {
"@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0",
"@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0",
"bull": "^3.3 || ^4.0.0"
}
},
"node_modules/@nestjs/bull-shared": {
"version": "11.0.4",
"resolved": "https://registry.npmjs.org/@nestjs/bull-shared/-/bull-shared-11.0.4.tgz",
"integrity": "sha512-VBJcDHSAzxQnpcDfA0kt9MTGUD1XZzfByV70su0W0eDCQ9aqIEBlzWRW21tv9FG9dIut22ysgDidshdjlnczLw==",
"license": "MIT",
"dependencies": {
"tslib": "2.8.1"
},
"peerDependencies": {
"@nestjs/common": "^10.0.0 || ^11.0.0",
"@nestjs/core": "^10.0.0 || ^11.0.0"
}
},
"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.10",
"resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-11.0.10.tgz",
@ -2329,6 +2492,33 @@
}
}
},
"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/config/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/@nestjs/core": {
"version": "11.1.7",
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.7.tgz",
@ -2370,6 +2560,39 @@
}
}
},
"node_modules/@nestjs/event-emitter": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@nestjs/event-emitter/-/event-emitter-3.0.1.tgz",
"integrity": "sha512-0Ln/x+7xkU6AJFOcQI9tIhUMXVF7D5itiaQGOyJbXtlAfAIt8gzDdJm+Im7cFzKoWkiW5nCXCPh6GSvdQd/3Dw==",
"license": "MIT",
"dependencies": {
"eventemitter2": "6.4.9"
},
"peerDependencies": {
"@nestjs/common": "^10.0.0 || ^11.0.0",
"@nestjs/core": "^10.0.0 || ^11.0.0"
}
},
"node_modules/@nestjs/mapped-types": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.1.0.tgz",
"integrity": "sha512-W+n+rM69XsFdwORF11UqJahn4J3xi4g/ZEOlJNL6KoW5ygWSmBB2p0S2BZ4FQeS/NDH72e6xIcu35SfJnE8bXw==",
"license": "MIT",
"peerDependencies": {
"@nestjs/common": "^10.0.0 || ^11.0.0",
"class-transformer": "^0.4.0 || ^0.5.0",
"class-validator": "^0.13.0 || ^0.14.0",
"reflect-metadata": "^0.1.12 || ^0.2.0"
},
"peerDependenciesMeta": {
"class-transformer": {
"optional": true
},
"class-validator": {
"optional": true
}
}
},
"node_modules/@nestjs/platform-express": {
"version": "11.1.7",
"resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.7.tgz",
@ -2391,6 +2614,19 @@
"@nestjs/core": "^11.0.0"
}
},
"node_modules/@nestjs/schedule": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-6.0.1.tgz",
"integrity": "sha512-v3yO6cSPAoBSSyH67HWnXHzuhPhSNZhRmLY38JvCt2sqY8sPMOODpcU1D79iUMFf7k16DaMEbL4Mgx61ZhiC8Q==",
"license": "MIT",
"dependencies": {
"cron": "4.3.3"
},
"peerDependencies": {
"@nestjs/common": "^10.0.0 || ^11.0.0",
"@nestjs/core": "^10.0.0 || ^11.0.0"
}
},
"node_modules/@nestjs/schematics": {
"version": "11.0.9",
"resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-11.0.9.tgz",
@ -2489,6 +2725,39 @@
"tslib": "^2.1.0"
}
},
"node_modules/@nestjs/swagger": {
"version": "11.2.1",
"resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-11.2.1.tgz",
"integrity": "sha512-1MS7xf0pzc1mofG53xrrtrurnziafPUHkqzRm4YUVPA/egeiMaSerQBD/feiAeQ2BnX0WiLsTX4HQFO0icvOjQ==",
"license": "MIT",
"dependencies": {
"@microsoft/tsdoc": "0.15.1",
"@nestjs/mapped-types": "2.1.0",
"js-yaml": "4.1.0",
"lodash": "4.17.21",
"path-to-regexp": "8.3.0",
"swagger-ui-dist": "5.29.4"
},
"peerDependencies": {
"@fastify/static": "^8.0.0",
"@nestjs/common": "^11.0.1",
"@nestjs/core": "^11.0.1",
"class-transformer": "*",
"class-validator": "*",
"reflect-metadata": "^0.1.12 || ^0.2.0"
},
"peerDependenciesMeta": {
"@fastify/static": {
"optional": true
},
"class-transformer": {
"optional": true
},
"class-validator": {
"optional": true
}
}
},
"node_modules/@nestjs/testing": {
"version": "11.1.7",
"resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.1.7.tgz",
@ -2703,6 +2972,78 @@
"@prisma/debug": "6.17.1"
}
},
"node_modules/@redis/bloom": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz",
"integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==",
"license": "MIT",
"peerDependencies": {
"@redis/client": "^1.0.0"
}
},
"node_modules/@redis/client": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.1.tgz",
"integrity": "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==",
"license": "MIT",
"dependencies": {
"cluster-key-slot": "1.1.2",
"generic-pool": "3.9.0",
"yallist": "4.0.0"
},
"engines": {
"node": ">=14"
}
},
"node_modules/@redis/client/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"license": "ISC"
},
"node_modules/@redis/graph": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.1.tgz",
"integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==",
"license": "MIT",
"peerDependencies": {
"@redis/client": "^1.0.0"
}
},
"node_modules/@redis/json": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.7.tgz",
"integrity": "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==",
"license": "MIT",
"peerDependencies": {
"@redis/client": "^1.0.0"
}
},
"node_modules/@redis/search": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@redis/search/-/search-1.2.0.tgz",
"integrity": "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==",
"license": "MIT",
"peerDependencies": {
"@redis/client": "^1.0.0"
}
},
"node_modules/@redis/time-series": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.1.0.tgz",
"integrity": "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==",
"license": "MIT",
"peerDependencies": {
"@redis/client": "^1.0.0"
}
},
"node_modules/@scarf/scarf": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz",
"integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==",
"hasInstallScript": true,
"license": "Apache-2.0"
},
"node_modules/@sinclair/typebox": {
"version": "0.34.41",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz",
@ -2979,6 +3320,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/luxon": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.7.1.tgz",
"integrity": "sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg==",
"license": "MIT"
},
"node_modules/@types/methods": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz",
@ -3081,6 +3428,12 @@
"@types/superagent": "^8.1.0"
}
},
"node_modules/@types/validator": {
"version": "13.15.3",
"resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.3.tgz",
"integrity": "sha512-7bcUmDyS6PN3EuD9SlGGOxM77F8WLVsrwkxyWxKnxzmXoequ6c7741QBrANq6htVRGOITJ7z72mTP6Z4XyuG+Q==",
"license": "MIT"
},
"node_modules/@types/yargs": {
"version": "17.0.33",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
@ -4047,7 +4400,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true,
"license": "Python-2.0"
},
"node_modules/array-timsort": {
@ -4364,6 +4716,25 @@
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"license": "MIT"
},
"node_modules/bull": {
"version": "4.16.5",
"resolved": "https://registry.npmjs.org/bull/-/bull-4.16.5.tgz",
"integrity": "sha512-lDsx2BzkKe7gkCYiT5Acj02DpTwDznl/VNN7Psn7M3USPG7Vs/BaClZJJTAG+ufAR9++N1/NiUTdaFBWDIl5TQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"cron-parser": "^4.9.0",
"get-port": "^5.1.1",
"ioredis": "^5.3.2",
"lodash": "^4.17.21",
"msgpackr": "^1.11.2",
"semver": "^7.5.2",
"uuid": "^8.3.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/busboy": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
@ -4413,6 +4784,29 @@
}
}
},
"node_modules/cache-manager": {
"version": "7.2.4",
"resolved": "https://registry.npmjs.org/cache-manager/-/cache-manager-7.2.4.tgz",
"integrity": "sha512-skmhkqXjPCBmrb70ctEx4zwFk7vb0RdFXlVGYWnFZ8pKvkzdFrFFKSJ1IaKduGfkryHOJvb7q2PkGmonmL+UGw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@cacheable/utils": "^2.1.0",
"keyv": "^5.5.3"
}
},
"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/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",
@ -4576,6 +4970,17 @@
"dev": true,
"license": "MIT"
},
"node_modules/class-validator": {
"version": "0.14.2",
"resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.2.tgz",
"integrity": "sha512-3kMVRF2io8N8pY1IFIXlho9r8IPUUIfHe2hYVtiebvAzU2XeQFXTv+XI4WX+TnXmtwXMDcjngcpkiPM0O9PvLw==",
"license": "MIT",
"dependencies": {
"@types/validator": "^13.11.8",
"libphonenumber-js": "^1.11.1",
"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",
@ -4694,6 +5099,15 @@
"node": ">=0.8"
}
},
"node_modules/cluster-key-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
"integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/co": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@ -4925,6 +5339,32 @@
"dev": true,
"license": "MIT"
},
"node_modules/cron": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/cron/-/cron-4.3.3.tgz",
"integrity": "sha512-B/CJj5yL3sjtlun6RtYHvoSB26EmQ2NUmhq9ZiJSyKIM4K/fqfh9aelDFlIayD2YMeFZqWLi9hHV+c+pq2Djkw==",
"license": "MIT",
"dependencies": {
"@types/luxon": "~3.7.0",
"luxon": "~3.7.0"
},
"engines": {
"node": ">=18.x"
}
},
"node_modules/cron-parser": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz",
"integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==",
"license": "MIT",
"peer": true,
"dependencies": {
"luxon": "^3.2.1"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@ -5029,6 +5469,16 @@
"node": ">=0.4.0"
}
},
"node_modules/denque": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
"license": "Apache-2.0",
"peer": true,
"engines": {
"node": ">=0.10"
}
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@ -5045,6 +5495,17 @@
"devOptional": true,
"license": "MIT"
},
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"license": "Apache-2.0",
"optional": true,
"peer": true,
"engines": {
"node": ">=8"
}
},
"node_modules/detect-newline": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
@ -5080,7 +5541,6 @@
"version": "16.6.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
"integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
"devOptional": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
@ -5089,6 +5549,21 @@
"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",
@ -5503,6 +5978,12 @@
"node": ">= 0.6"
}
},
"node_modules/eventemitter2": {
"version": "6.4.9",
"resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz",
"integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==",
"license": "MIT"
},
"node_modules/events": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
@ -5860,6 +6341,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",
@ -6041,6 +6532,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/generic-pool": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz",
"integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==",
"license": "MIT",
"engines": {
"node": ">= 4"
}
},
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@ -6095,6 +6595,19 @@
"node": ">=8.0.0"
}
},
"node_modules/get-port": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz",
"integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
@ -6473,6 +6986,31 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/ioredis": {
"version": "5.8.2",
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.8.2.tgz",
"integrity": "sha512-C6uC+kleiIMmjViJINWk80sOQw5lEzse1ZmvD+S/s8p8CWapftSaC+kocGTx6xrbrJ4WmYQGC08ffHLr6ToR6Q==",
"license": "MIT",
"peer": true,
"dependencies": {
"@ioredis/commands": "1.4.0",
"cluster-key-slot": "^1.1.0",
"debug": "^4.3.4",
"denque": "^2.1.0",
"lodash.defaults": "^4.2.0",
"lodash.isarguments": "^3.1.0",
"redis-errors": "^1.2.0",
"redis-parser": "^3.0.0",
"standard-as-callback": "^2.1.0"
},
"engines": {
"node": ">=12.22.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/ioredis"
}
},
"node_modules/ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
@ -7483,7 +8021,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true,
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
@ -7567,13 +8104,13 @@
}
},
"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.5.3",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-5.5.3.tgz",
"integrity": "sha512-h0Un1ieD+HUrzBH6dJXhod3ifSghk5Hw/2Y4/KHBziPlZecrFyE9YOTPU6eOs0V9pYl8gOs86fkr/KN8lUX39A==",
"license": "MIT",
"peer": true,
"dependencies": {
"json-buffer": "3.0.1"
"@keyv/serialize": "^1.1.1"
}
},
"node_modules/leven": {
@ -7600,6 +8137,12 @@
"node": ">= 0.8.0"
}
},
"node_modules/libphonenumber-js": {
"version": "1.12.24",
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.24.tgz",
"integrity": "sha512-l5IlyL9AONj4voSd7q9xkuQOL4u8Ty44puTic7J88CmdXkxfGsRfoVLXHCxppwehgpb/Chdb80FFehHqjN3ItQ==",
"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",
@ -7660,9 +8203,22 @@
"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": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
"license": "MIT",
"peer": true
},
"node_modules/lodash.isarguments": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
"integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==",
"license": "MIT",
"peer": true
},
"node_modules/lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
@ -7704,6 +8260,15 @@
"yallist": "^3.0.2"
}
},
"node_modules/luxon": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz",
"integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==",
"license": "MIT",
"engines": {
"node": ">=12"
}
},
"node_modules/magic-string": {
"version": "0.30.17",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
@ -7938,6 +8503,39 @@
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/msgpackr": {
"version": "1.11.5",
"resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.5.tgz",
"integrity": "sha512-UjkUHN0yqp9RWKy0Lplhh+wlpdt9oQBYgULZOiFhV3VclSF1JnSQWZ5r9gORQlNYaUKQoR8itv7g7z1xDDuACA==",
"license": "MIT",
"peer": true,
"optionalDependencies": {
"msgpackr-extract": "^3.0.2"
}
},
"node_modules/msgpackr-extract": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz",
"integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"node-gyp-build-optional-packages": "5.2.2"
},
"bin": {
"download-msgpackr-prebuilds": "bin/download-prebuilds.js"
},
"optionalDependencies": {
"@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3",
"@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3",
"@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3",
"@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3",
"@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3",
"@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3"
}
},
"node_modules/multer": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz",
@ -8072,6 +8670,22 @@
"devOptional": true,
"license": "MIT"
},
"node_modules/node-gyp-build-optional-packages": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz",
"integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"detect-libc": "^2.0.1"
},
"bin": {
"node-gyp-build-optional-packages": "bin.js",
"node-gyp-build-optional-packages-optional": "optional.js",
"node-gyp-build-optional-packages-test": "build-test.js"
}
},
"node_modules/node-int64": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
@ -8810,6 +9424,46 @@
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/redis": {
"version": "4.7.1",
"resolved": "https://registry.npmjs.org/redis/-/redis-4.7.1.tgz",
"integrity": "sha512-S1bJDnqLftzHXHP8JsT5II/CtHWQrASX5K96REjWjlmWKrviSOLWmM7QnRLstAWsu1VBBV1ffV6DzCvxNP0UJQ==",
"license": "MIT",
"workspaces": [
"./packages/*"
],
"dependencies": {
"@redis/bloom": "1.2.0",
"@redis/client": "1.6.1",
"@redis/graph": "1.1.1",
"@redis/json": "1.0.7",
"@redis/search": "1.2.0",
"@redis/time-series": "1.1.0"
}
},
"node_modules/redis-errors": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
"integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=4"
}
},
"node_modules/redis-parser": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
"integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==",
"license": "MIT",
"peer": true,
"dependencies": {
"redis-errors": "^1.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/reflect-metadata": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
@ -8999,7 +9653,6 @@
"version": "7.7.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@ -9240,6 +9893,13 @@
"node": ">=8"
}
},
"node_modules/standard-as-callback": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
"integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==",
"license": "MIT",
"peer": true
},
"node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
@ -9517,6 +10177,15 @@
"node": ">=8"
}
},
"node_modules/swagger-ui-dist": {
"version": "5.29.4",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.29.4.tgz",
"integrity": "sha512-gJFDz/gyLOCQtWwAgqs6Rk78z9ONnqTnlW11gimG9nLap8drKa3AJBKpzIQMIjl5PD2Ix+Tn+mc/tfoT2tgsng==",
"license": "Apache-2.0",
"dependencies": {
"@scarf/scarf": "=1.4.0"
}
},
"node_modules/symbol-observable": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz",
@ -10256,6 +10925,16 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
"node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"license": "MIT",
"peer": true,
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
@ -10278,6 +10957,15 @@
"node": ">=10.12.0"
}
},
"node_modules/validator": {
"version": "13.15.15",
"resolved": "https://registry.npmjs.org/validator/-/validator-13.15.15.tgz",
"integrity": "sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==",
"license": "MIT",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",

View File

@ -20,10 +20,18 @@
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@nestjs/bull": "^11.0.4",
"@nestjs/cache-manager": "^3.0.1",
"@nestjs/common": "^11.0.1",
"@nestjs/config": "^4.0.2",
"@nestjs/core": "^11.0.1",
"@nestjs/event-emitter": "^3.0.1",
"@nestjs/platform-express": "^11.0.1",
"@nestjs/schedule": "^6.0.1",
"@nestjs/swagger": "^11.2.1",
"@prisma/client": "^6.17.1",
"cache-manager-redis-store": "^3.0.1",
"class-validator": "^0.14.2",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1"
},

View File

@ -0,0 +1,38 @@
/*
Warnings:
- Added the required column `country` to the `Partner` table without a default value. This is not possible if the table is not empty.
- Added the required column `passwordHash` to the `Partner` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "Partner" ADD COLUMN "companyInfo" JSONB,
ADD COLUMN "country" TEXT NOT NULL,
ADD COLUMN "keysRotatedAt" TIMESTAMP(3),
ADD COLUMN "passwordHash" TEXT NOT NULL,
ALTER COLUMN "status" SET DEFAULT 'PENDING';
-- CreateTable
CREATE TABLE "AuthSession" (
"id" TEXT NOT NULL,
"sessionId" TEXT NOT NULL,
"partnerId" TEXT NOT NULL,
"userId" TEXT,
"msisdn" TEXT NOT NULL,
"operator" TEXT NOT NULL,
"country" TEXT NOT NULL,
"authMethod" TEXT NOT NULL,
"challengeId" TEXT,
"status" TEXT NOT NULL,
"expiresAt" TIMESTAMP(3) NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "AuthSession_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "AuthSession_sessionId_key" ON "AuthSession"("sessionId");
-- AddForeignKey
ALTER TABLE "AuthSession" ADD CONSTRAINT "AuthSession_partnerId_fkey" FOREIGN KEY ("partnerId") REFERENCES "Partner"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@ -39,22 +39,7 @@ enum SubscriptionStatus {
FAILED
}
model Partner {
id String @id @default(cuid())
name String
email String @unique
apiKey String @unique
secretKey String
status String
callbacks Json?
metadata Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
users User[]
subscriptions Subscription[]
payments Payment[]
}
model Operator {
id String @id @default(cuid())
@ -174,3 +159,43 @@ model Webhook {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Partner {
id String @id @default(cuid())
name String
email String @unique
passwordHash String
apiKey String @unique
secretKey String
status String @default("PENDING")
companyInfo Json?
callbacks Json?
country String
metadata Json?
keysRotatedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
users User[]
subscriptions Subscription[]
payments Payment[]
authSessions AuthSession[]
}
model AuthSession {
id String @id @default(cuid())
sessionId String @unique
partnerId String
userId String?
msisdn String
operator String
country String
authMethod String
challengeId String?
status String
expiresAt DateTime
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
partner Partner @relation(fields: [partnerId], references: [id])
}

View File

@ -30,7 +30,7 @@ export interface OperatorConfig {
export const operatorsConfig = (): Record<string, OperatorConfig> => ({
ORANGE_CIV: {
name: 'Orange Côte d\'Ivoire',
name: 'Orange Côte d Ivoire',
baseUrl: process.env.ORANGE_CIV_BASE_URL || 'https://api.bizao.com',
authType: 'OTP',
endpoints: {

View File

@ -7,7 +7,7 @@ async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Global prefix
app.setGlobalPrefix('api/v2');
app.setGlobalPrefix('api/v1');
// Validation
app.useGlobalPipes(

View File

@ -0,0 +1,31 @@
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { JwtStrategy } from './strategies/jwt.strategy';
import { ApiKeyStrategy } from './strategies/api-key.strategy';
import { PrismaService } from '../../shared/services/prisma.service';
import { OperatorsModule } from '../operators/operators.module';
@Module({
imports: [
PassportModule.register({ defaultStrategy: 'jwt' }),
JwtModule.registerAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
secret: configService.get<string>('app.jwtSecret'),
signOptions: {
expiresIn: configService.get<string>('app.jwtExpiresIn'),
},
}),
inject: [ConfigService],
}),
OperatorsModule,
],
controllers: [AuthController],
providers: [AuthService, JwtStrategy, ApiKeyStrategy, PrismaService],
exports: [AuthService, JwtModule],
})
export class AuthModule {}

View File

@ -0,0 +1,245 @@
import {
Injectable,
UnauthorizedException,
BadRequestException,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { PrismaService } from '../../shared/services/prisma.service';
import { OperatorsService } from '../operators/operators.service';
import * as bcrypt from 'bcrypt';
import { AuthInitDto, AuthValidateDto, LoginDto } from './dto/auth.dto';
@Injectable()
export class AuthService {
constructor(
private readonly prisma: PrismaService,
private readonly jwtService: JwtService,
private readonly operatorsService: OperatorsService,
) {}
async initializeUserAuth(partnerId: string, dto: AuthInitDto) {
// Vérifier le partenaire
const partner = await this.prisma.partner.findUnique({
where: { id: partnerId },
});
if (!partner || partner.status !== 'ACTIVE') {
throw new UnauthorizedException('Invalid partner');
}
// Déterminer l'opérateur basé sur le numéro
const operator = this.detectOperator(dto.msisdn, dto.country);
// Obtenir l'adaptateur approprié
const adapter = this.operatorsService.getAdapter(operator, dto.country);
// Initialiser l'authentification avec l'opérateur
const authResponse = await adapter.initializeAuth({
msisdn: dto.msisdn,
country: dto.country,
metadata: dto.metadata,
});
// Créer une session temporaire
const session = await this.prisma.authSession.create({
data: {
sessionId: authResponse.sessionId,
partnerId: partnerId,
msisdn: dto.msisdn,
operator: operator,
country: dto.country,
authMethod: dto.authMethod,
challengeId: authResponse.challengeId,
status: 'PENDING',
expiresAt: authResponse.expiresAt,
},
});
return {
sessionId: session.sessionId,
authMethod: dto.authMethod,
status: 'PENDING',
redirectUrl: authResponse.redirectUrl,
challengeId: authResponse.challengeId,
expiresAt: authResponse.expiresAt,
};
}
async validateUserAuth(dto: AuthValidateDto) {
// Récupérer la session
const session = await this.prisma.authSession.findUnique({
where: { sessionId: dto.sessionId },
});
if (!session) {
throw new BadRequestException('Invalid session');
}
if (session.status !== 'PENDING') {
throw new BadRequestException('Session already processed');
}
if (new Date() > session.expiresAt) {
throw new BadRequestException('Session expired');
}
// Obtenir l'adaptateur
const adapter = this.operatorsService.getAdapter(
session.operator,
session.country,
);
// Valider avec l'opérateur
const validationResponse = await adapter.validateAuth({
challengeId: session.challengeId,
otpCode: dto.otpCode,
msisdn: session.msisdn,
country: session.country,
});
if (!validationResponse.success) {
await this.prisma.authSession.update({
where: { id: session.id },
data: { status: 'FAILED' },
});
throw new UnauthorizedException('Authentication failed');
}
// Créer ou mettre à jour l'utilisateur
const user = await this.prisma.user.upsert({
where: { msisdn: session.msisdn },
update: {
userToken: validationResponse.userToken,
userAlias: validationResponse.userAlias,
updatedAt: new Date(),
},
create: {
msisdn: session.msisdn,
userToken: validationResponse.userToken,
userAlias: validationResponse.userAlias,
operatorId: await this.getOperatorId(session.operator, session.country),
partnerId: session.partnerId,
country: session.country,
},
});
// Mettre à jour la session
await this.prisma.authSession.update({
where: { id: session.id },
data: {
status: 'SUCCESS',
userId: user.id,
},
});
// Créer un JWT pour le partenaire
const payload = {
userId: user.id,
partnerId: session.partnerId,
msisdn: user.msisdn,
operator: session.operator,
};
return {
success: true,
accessToken: this.jwtService.sign(payload),
userToken: validationResponse.userToken,
userAlias: validationResponse.userAlias,
msisdn: session.msisdn,
operator: session.operator,
country: session.country,
expiresAt: validationResponse.expiresAt,
};
}
async loginPartner(dto: LoginDto) {
const partner = await this.prisma.partner.findUnique({
where: { email: dto.email },
});
if (!partner) {
throw new UnauthorizedException('Invalid credentials');
}
const isPasswordValid = await bcrypt.compare(
dto.password,
partner.passwordHash,
);
if (!isPasswordValid) {
throw new UnauthorizedException('Invalid credentials');
}
const payload = {
partnerId: partner.id,
email: partner.email,
type: 'partner',
};
return {
accessToken: this.jwtService.sign(payload),
partner: {
id: partner.id,
name: partner.name,
email: partner.email,
status: partner.status,
},
};
}
private detectOperator(msisdn: string, country: string): string {
// Logique pour détecter l'opérateur basé sur le préfixe
const prefixMap = {
CI: {
'07': 'ORANGE',
'08': 'ORANGE',
'09': 'ORANGE',
'04': 'MTN',
'05': 'MTN',
'06': 'MTN',
'01': 'MOOV',
},
SN: {
'77': 'ORANGE',
'78': 'ORANGE',
'76': 'FREE',
'70': 'EXPRESSO',
},
// Ajouter d'autres pays
};
const countryPrefixes = prefixMap[country];
if (!countryPrefixes) {
throw new BadRequestException(`Country ${country} not supported`);
}
const prefix = msisdn.substring(0, 2);
const operator = countryPrefixes[prefix];
if (!operator) {
throw new BadRequestException(`Cannot detect operator for ${msisdn}`);
}
return operator;
}
private async getOperatorId(
operatorCode: string,
country: string,
): Promise<string> {
const operator = await this.prisma.operator.findFirst({
where: {
code: operatorCode as any,
country: country,
},
});
if (!operator) {
throw new BadRequestException(
`Operator ${operatorCode} not found in ${country}`,
);
}
return operator.id;
}
}

View File

@ -0,0 +1,50 @@
import { IsString, IsEnum, IsOptional, IsMobilePhone } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class AuthInitDto {
@ApiProperty()
@IsMobilePhone()
msisdn: string;
@ApiProperty()
@IsString()
country: string;
@ApiProperty({ enum: ['OTP_SMS', 'REDIRECT_3G', 'SMS_MO', 'USSD'] })
@IsEnum(['OTP_SMS', 'REDIRECT_3G', 'SMS_MO', 'USSD'])
authMethod: string;
@ApiProperty({ required: false })
@IsOptional()
redirectUrl?: string;
@ApiProperty({ required: false })
@IsOptional()
metadata?: Record<string, any>;
}
export class AuthValidateDto {
@ApiProperty()
@IsString()
sessionId: string;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
otpCode?: string;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
challengeResponse?: string;
}
export class LoginDto {
@ApiProperty()
@IsString()
email: string;
@ApiProperty()
@IsString()
password: string;
}

View File

@ -0,0 +1,23 @@
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private configService: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: configService.get<string>('app.jwtSecret'),
});
}
async validate(payload: any) {
return {
userId: payload.userId,
partnerId: payload.partnerId,
email: payload.email,
};
}
}

View File

@ -14,11 +14,11 @@ export class OperatorAdapterFactory {
const key = `${operator}_${country}`.toUpperCase();
const adapterMap = {
'ORANGE_CI': this.orangeAdapter,
'ORANGE_SN': this.orangeAdapter,
'ORANGE_CM': this.orangeAdapter,
'MTN_CI': this.mtnAdapter,
'MTN_CM': this.mtnAdapter,
ORANGE_CI: this.orangeAdapter,
ORANGE_SN: this.orangeAdapter,
ORANGE_CM: this.orangeAdapter,
MTN_CI: this.mtnAdapter,
MTN_CM: this.mtnAdapter,
// Ajouter d'autres mappings
};

View File

@ -4,7 +4,9 @@ export interface IOperatorAdapter {
charge(params: ChargeParams): Promise<ChargeResponse>;
refund(params: RefundParams): Promise<RefundResponse>;
sendSms(params: SmsParams): Promise<SmsResponse>;
createSubscription?(params: SubscriptionParams): Promise<SubscriptionResponse>;
createSubscription?(
params: SubscriptionParams,
): Promise<SubscriptionResponse>;
cancelSubscription?(subscriptionId: string): Promise<void>;
}

View File

@ -123,8 +123,8 @@ export class OrangeAdapter implements IOperatorAdapter {
);
const result = response.data.challenge.result;
const userToken = result.find(r => r.type === 'OrangeApiToken')?.value;
const userAlias = result.find(r => r.type === 'ise2')?.value;
const userToken = result.find((r) => r.type === 'OrangeApiToken')?.value;
const userAlias = result.find((r) => r.type === 'ise2')?.value;
return {
success: true,

View File

@ -5,19 +5,25 @@ export class OrangeTransformer {
transformChargeResponse(bizaoResponse: any): any {
return {
paymentId: bizaoResponse.amountTransaction?.serverReferenceCode,
status: this.mapStatus(bizaoResponse.amountTransaction?.transactionOperationStatus),
status: this.mapStatus(
bizaoResponse.amountTransaction?.transactionOperationStatus,
),
operatorReference: bizaoResponse.amountTransaction?.serverReferenceCode,
amount: parseFloat(bizaoResponse.amountTransaction?.paymentAmount?.totalAmountCharged),
currency: bizaoResponse.amountTransaction?.paymentAmount?.chargingInformation?.currency,
amount: parseFloat(
bizaoResponse.amountTransaction?.paymentAmount?.totalAmountCharged,
),
currency:
bizaoResponse.amountTransaction?.paymentAmount?.chargingInformation
?.currency,
createdAt: new Date(),
};
}
private mapStatus(bizaoStatus: string): string {
const statusMap = {
'Charged': 'SUCCESS',
'Failed': 'FAILED',
'Pending': 'PENDING',
Charged: 'SUCCESS',
Failed: 'FAILED',
Pending: 'PENDING',
};
return statusMap[bizaoStatus] || 'PENDING';
}

View File

@ -0,0 +1,76 @@
import {
IsString,
IsEmail,
IsOptional,
IsObject,
MinLength,
} from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class CreatePartnerDto {
@ApiProperty()
@IsString()
name: string;
@ApiProperty()
@IsEmail()
email: string;
@ApiProperty()
@IsString()
@MinLength(8)
password: string;
@ApiProperty()
@IsString()
country: string;
@ApiProperty({ required: false })
@IsOptional()
@IsObject()
companyInfo?: {
legalName: string;
taxId: string;
address: string;
phone?: string;
website?: string;
};
@ApiProperty({ required: false })
@IsOptional()
metadata?: Record<string, any>;
}
export class UpdateCallbacksDto {
@ApiProperty({ required: false })
@IsOptional()
headerEnrichment?: {
url: string;
method: string;
headers?: Record<string, string>;
};
@ApiProperty({ required: false })
@IsOptional()
subscription?: {
onCreate?: string;
onRenew?: string;
onCancel?: string;
onExpire?: string;
};
@ApiProperty({ required: false })
@IsOptional()
payment?: {
onSuccess?: string;
onFailure?: string;
onRefund?: string;
};
@ApiProperty({ required: false })
@IsOptional()
authentication?: {
onSuccess?: string;
onFailure?: string;
};
}

View File

@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { PartnersController } from './partners.controller';
import { PartnersService } from './partners.service';
import { PrismaService } from '../../shared/services/prisma.service';
@Module({
controllers: [PartnersController],
providers: [PartnersService, PrismaService],
exports: [PartnersService],
})
export class PartnersModule {}

View File

@ -0,0 +1,184 @@
import {
Injectable,
ConflictException,
NotFoundException,
} from '@nestjs/common';
import { PrismaService } from '../../shared/services/prisma.service';
import * as bcrypt from 'bcrypt';
import * as crypto from 'crypto';
import { CreatePartnerDto, UpdateCallbacksDto } from './dto/partner.dto';
@Injectable()
export class PartnersService {
constructor(private readonly prisma: PrismaService) {}
async register(dto: CreatePartnerDto) {
// Vérifier si l'email existe déjà
const existingPartner = await this.prisma.partner.findUnique({
where: { email: dto.email },
});
if (existingPartner) {
throw new ConflictException('Email already registered');
}
// Générer les clés API
const apiKey = this.generateApiKey();
const secretKey = this.generateSecretKey();
// Hasher le mot de passe
const passwordHash = await bcrypt.hash(dto.password, 10);
// Créer le partenaire
const partner = await this.prisma.partner.create({
data: {
name: dto.name,
email: dto.email,
passwordHash: passwordHash,
apiKey: apiKey,
secretKey: secretKey,
status: 'PENDING',
companyInfo: dto.companyInfo,
country: dto.country,
metadata: dto.metadata,
},
});
return {
partnerId: partner.id,
apiKey: partner.apiKey,
secretKey: partner.secretKey,
status: partner.status,
message: 'Partner registered successfully. Awaiting approval.',
};
}
async updateCallbacks(partnerId: string, dto: UpdateCallbacksDto) {
const partner = await this.prisma.partner.findUnique({
where: { id: partnerId },
});
if (!partner) {
throw new NotFoundException('Partner not found');
}
const updatedPartner = await this.prisma.partner.update({
where: { id: partnerId },
data: {
callbacks: dto,
},
});
return {
partnerId: updatedPartner.id,
callbacks: updatedPartner.callbacks,
};
}
async getPartner(partnerId: string) {
const partner = await this.prisma.partner.findUnique({
where: { id: partnerId },
select: {
id: true,
name: true,
email: true,
status: true,
callbacks: true,
companyInfo: true,
createdAt: true,
_count: {
select: {
users: true,
subscriptions: true,
payments: true,
},
},
},
});
if (!partner) {
throw new NotFoundException('Partner not found');
}
return partner;
}
async getPartnerStats(partnerId: string) {
const today = new Date();
today.setHours(0, 0, 0, 0);
const [totalUsers, activeSubscriptions, todayPayments, monthRevenue] =
await Promise.all([
this.prisma.user.count({
where: { partnerId },
}),
this.prisma.subscription.count({
where: {
partnerId,
status: 'ACTIVE',
},
}),
this.prisma.payment.count({
where: {
partnerId,
createdAt: { gte: today },
},
}),
this.prisma.payment.aggregate({
where: {
partnerId,
status: 'SUCCESS',
createdAt: {
gte: new Date(today.getFullYear(), today.getMonth(), 1),
},
},
_sum: {
amount: true,
},
}),
]);
return {
totalUsers,
activeSubscriptions,
todayPayments,
monthRevenue: monthRevenue._sum.amount || 0,
};
}
async regenerateKeys(partnerId: string) {
const partner = await this.prisma.partner.findUnique({
where: { id: partnerId },
});
if (!partner) {
throw new NotFoundException('Partner not found');
}
const newApiKey = this.generateApiKey();
const newSecretKey = this.generateSecretKey();
const updatedPartner = await this.prisma.partner.update({
where: { id: partnerId },
data: {
apiKey: newApiKey,
secretKey: newSecretKey,
keysRotatedAt: new Date(),
},
});
return {
apiKey: updatedPartner.apiKey,
secretKey: updatedPartner.secretKey,
message: 'Keys regenerated successfully',
};
}
private generateApiKey(): string {
return `pk_${crypto.randomBytes(32).toString('hex')}`;
}
private generateSecretKey(): string {
return `sk_${crypto.randomBytes(32).toString('hex')}`;
}
}

View File

@ -0,0 +1,24 @@
import { Module } from '@nestjs/common';
import { BullModule } from '@nestjs/bull';
import { PaymentsController } from './payments.controller';
import { PaymentsService } from './payments.service';
import { PaymentProcessor } from './processors/payment.processor';
import { WebhookService } from './services/webhook.service';
import { PrismaService } from '../../shared/services/prisma.service';
import { OperatorsModule } from '../operators/operators.module';
@Module({
imports: [
BullModule.registerQueue({
name: 'payments',
}),
BullModule.registerQueue({
name: 'webhooks',
}),
OperatorsModule,
],
controllers: [PaymentsController],
providers: [PaymentsService, PaymentProcessor, WebhookService, PrismaService],
exports: [PaymentsService],
})
export class PaymentsModule {}

View File

@ -59,7 +59,8 @@ export class PaymentsService {
const updatedPayment = await this.prisma.payment.update({
where: { id: payment.id },
data: {
status: result.status === 'SUCCESS'
status:
result.status === 'SUCCESS'
? PaymentStatus.SUCCESS
: PaymentStatus.FAILED,
operatorReference: result.operatorReference,

View File

@ -0,0 +1,49 @@
import { Process, Processor } from '@nestjs/bull';
import { Job } from 'bull';
import { PaymentsService } from '../payments.service';
import { WebhookService } from '../services/webhook.service';
@Processor('payments')
export class PaymentProcessor {
constructor(
private readonly paymentsService: PaymentsService,
private readonly webhookService: WebhookService,
) {}
@Process('process-payment')
async handlePayment(job: Job) {
const { paymentId } = job.data;
try {
// Traiter le paiement
const result = await this.paymentsService.processPayment(paymentId);
// Envoyer le webhook
if (result.callbackUrl) {
await this.webhookService.send({
url: result.callbackUrl,
event:
result.status === 'SUCCESS' ? 'PAYMENT_SUCCESS' : 'PAYMENT_FAILED',
payload: result,
});
}
return result;
} catch (error) {
console.error(`Payment processing failed for ${paymentId}:`, error);
throw error;
}
}
@Process('retry-payment')
async handleRetry(job: Job) {
const { paymentId, attempt } = job.data;
try {
return await this.paymentsService.retryPayment(paymentId, attempt);
} catch (error) {
console.error(`Payment retry failed for ${paymentId}:`, error);
throw error;
}
}
}

View File

@ -136,7 +136,10 @@ export class SubscriptionsService {
await this.handlePaymentFailure(subscription.id);
}
} catch (error) {
console.error(`Failed to renew subscription ${subscription.id}:`, error);
console.error(
`Failed to renew subscription ${subscription.id}:`,
error,
);
await this.handlePaymentFailure(subscription.id);
}
}
@ -166,9 +169,13 @@ export class SubscriptionsService {
const now = new Date();
switch (trialPeriod.unit) {
case 'DAYS':
return new Date(now.getTime() + trialPeriod.duration * 24 * 60 * 60 * 1000);
return new Date(
now.getTime() + trialPeriod.duration * 24 * 60 * 60 * 1000,
);
case 'WEEKS':
return new Date(now.getTime() + trialPeriod.duration * 7 * 24 * 60 * 60 * 1000);
return new Date(
now.getTime() + trialPeriod.duration * 7 * 24 * 60 * 60 * 1000,
);
case 'MONTHS':
return new Date(now.setMonth(now.getMonth() + trialPeriod.duration));
default:

View File

@ -2,7 +2,10 @@ import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
export class PrismaService
extends PrismaClient
implements OnModuleInit, OnModuleDestroy
{
constructor() {
super({
log: ['query', 'info', 'warn', 'error'],