diff --git a/package.json b/package.json index de40d9a..aea9098 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "dependencies": { "@keyv/redis": "^5.1.6", "@nestjs/axios": "^4.0.1", + "@nestjs/bullmq": "^11.0.4", "@nestjs/cache-manager": "^3.1.3", "@nestjs/common": "^11.1.28", "@nestjs/config": "^4.0.4", @@ -33,9 +34,11 @@ "@nestjs/typeorm": "^11.0.3", "axios": "^1.18.1", "bcrypt": "^6.0.0", + "bullmq": "^5.81.3", "cache-manager": "^7.2.9", "class-transformer": "^0.5.1", "class-validator": "^0.15.1", + "ioredis": "^5.11.1", "joi": "^18.2.3", "keyv": "^5.6.0", "ms": "^2.1.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ff2ce86..210dff8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: '@nestjs/axios': specifier: ^4.0.1 version: 4.0.1(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.18.1)(rxjs@7.8.2) + '@nestjs/bullmq': + specifier: ^11.0.4 + version: 11.0.4(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.28)(bullmq@5.81.3) '@nestjs/cache-manager': specifier: ^3.1.3 version: 3.1.3(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.28)(cache-manager@7.2.9)(keyv@5.6.0)(rxjs@7.8.2) @@ -40,13 +43,16 @@ importers: version: 11.4.6(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.28)(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2) '@nestjs/typeorm': specifier: ^11.0.3 - version: 11.0.3(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.28)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@1.1.0(pg@8.22.0)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.9.3))) + version: 11.0.3(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.28)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@1.1.0(ioredis@5.11.1)(pg@8.22.0)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.9.3))) axios: specifier: ^1.18.1 version: 1.18.1 bcrypt: specifier: ^6.0.0 version: 6.0.0 + bullmq: + specifier: ^5.81.3 + version: 5.81.3 cache-manager: specifier: ^7.2.9 version: 7.2.9 @@ -56,6 +62,9 @@ importers: class-validator: specifier: ^0.15.1 version: 0.15.1 + ioredis: + specifier: ^5.11.1 + version: 5.11.1 joi: specifier: ^18.2.3 version: 18.2.3 @@ -82,7 +91,7 @@ importers: version: 7.8.2 typeorm: specifier: ^1.1.0 - version: 1.1.0(pg@8.22.0)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.9.3)) + version: 1.1.0(ioredis@5.11.1)(pg@8.22.0)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.9.3)) devDependencies: '@eslint/eslintrc': specifier: ^3.2.0 @@ -605,6 +614,9 @@ packages: '@types/node': optional: true + '@ioredis/commands@1.10.0': + resolution: {integrity: sha512-UmeW7z4LfctwoQ5wkhVzgq8tXkreED2xZGpX+Bg+zA+WJFZCT6c062AfCK/Dfk81xZnnwdhJCUMkitihRaoC2Q==} + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -737,6 +749,36 @@ packages: '@microsoft/tsdoc@0.16.0': resolution: {integrity: sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==} + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4': + resolution: {integrity: sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ==} + cpu: [arm64] + os: [darwin] + + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4': + resolution: {integrity: sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w==} + cpu: [x64] + os: [darwin] + + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.4': + resolution: {integrity: sha512-dgX0P/9wGPJeHFBG+ZmhgE6bmtMt7NP5CRBGyyktpopdk/mW4POnrpQsSLtKI1dwpc+pPLuXHDh6vvskyQE/sw==} + cpu: [arm64] + os: [linux] + + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.4': + resolution: {integrity: sha512-Tg3yX65f5GbtXLkrYEHE5oibZG9epyYWas7FogTTEJeDEF9JlXJzKgXaNhT3UXlTOeA+AfZpYZYZ0uPj7Cfquw==} + cpu: [arm] + os: [linux] + + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.4': + resolution: {integrity: sha512-8TNXMEjJc3QEy7R/x1INhgiU+XakDAFUzBhaz7+Rbrs8NH5UQeHQxxmzsSBJGyV6I1jW79undiQm8tOI+D+8FQ==} + cpu: [x64] + os: [linux] + + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4': + resolution: {integrity: sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ==} + cpu: [x64] + os: [win32] + '@napi-rs/wasm-runtime@1.1.6': resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} peerDependencies: @@ -750,6 +792,19 @@ packages: axios: ^1.3.1 rxjs: ^7.0.0 + '@nestjs/bull-shared@11.0.4': + resolution: {integrity: sha512-VBJcDHSAzxQnpcDfA0kt9MTGUD1XZzfByV70su0W0eDCQ9aqIEBlzWRW21tv9FG9dIut22ysgDidshdjlnczLw==} + peerDependencies: + '@nestjs/common': ^10.0.0 || ^11.0.0 + '@nestjs/core': ^10.0.0 || ^11.0.0 + + '@nestjs/bullmq@11.0.4': + resolution: {integrity: sha512-wBzK9raAVG0/6NTMdvLGM4/FQ1lsB35/pYS8L6a0SDgkTiLpd7mAjQ8R692oMx5s7IjvgntaZOuTUrKYLNfIkA==} + peerDependencies: + '@nestjs/common': ^10.0.0 || ^11.0.0 + '@nestjs/core': ^10.0.0 || ^11.0.0 + bullmq: ^3.0.0 || ^4.0.0 || ^5.0.0 + '@nestjs/cache-manager@3.1.3': resolution: {integrity: sha512-HMtiOfHz75NZX7mJn1VnZGLSachVI04TnUc5wvEogIaKwk5BDQHgtP5htxreizjv7oxKalJbuTyxtiF6bE+bgQ==} peerDependencies: @@ -1504,6 +1559,15 @@ packages: buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + bullmq@5.81.3: + resolution: {integrity: sha512-Q7uEH2G92rVjX3Yl2qw3+6VEO15uxehC6DfeTHQBQTehddcRDSDuquD9zuvDjxiCffXsPUaBwrRlvlbVcgKs7g==} + engines: {node: '>=12.22.0'} + peerDependencies: + redis: '>=5.0.0' + peerDependenciesMeta: + redis: + optional: true + busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} @@ -1598,6 +1662,10 @@ packages: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} + cluster-key-slot@1.1.1: + resolution: {integrity: sha512-rwHwUfXL40Chm1r08yrhU3qpUvdVlgkKNeyeGPOxnW8/SyVDvgRaed/Uz54AqWNaTCAThlj6QAs3TZcKI0xDEw==} + engines: {node: '>=0.10.0'} + cluster-key-slot@1.1.2: resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} engines: {node: '>=0.10.0'} @@ -1683,6 +1751,10 @@ packages: create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + cron-parser@4.9.0: + resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==} + engines: {node: '>=12.0.0'} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -1721,10 +1793,18 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + denque@2.1.0: + resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} + engines: {node: '>=0.10'} + depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + detect-newline@3.1.0: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} @@ -2177,6 +2257,10 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + ioredis@5.11.1: + resolution: {integrity: sha512-ehuGcf94bQXhfagULNXrJdfnWO38v070jxSx/qE87Kjzmu2fU7ro5EFAb+OPituLqgfyuQaym5DlrNydW2sJ9A==} + engines: {node: '>=12.22.0'} + ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} @@ -2517,6 +2601,10 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + luxon@3.7.2: + resolution: {integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==} + engines: {node: '>=12'} + magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} @@ -2603,6 +2691,13 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + msgpackr-extract@3.0.4: + resolution: {integrity: sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw==} + hasBin: true + + msgpackr@2.0.5: + resolution: {integrity: sha512-cef05H/dSYpLpqp3sj/qyZh5vhUYCalnaLO7j1yOmpsR0y/XwLVtK7r5gn+U/F7CTEfMowcGhlUQJDLcLf7jcA==} + multer@2.2.0: resolution: {integrity: sha512-6rdyFg2kLrMh9Jee7/BMPuV9lEAd7lLW2YUpF9/YxR7njyoUwwQ0ZPh3TaIY50Sw6vlyD2HW3wGOkTS4P79xrQ==} engines: {node: '>= 10.16.0'} @@ -2636,6 +2731,10 @@ packages: node-emoji@1.11.0: resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} + node-gyp-build-optional-packages@5.2.2: + resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==} + hasBin: true + node-gyp-build@4.8.4: resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} hasBin: true @@ -2893,6 +2992,14 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} + redis-errors@1.2.0: + resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} + engines: {node: '>=4'} + + redis-parser@3.0.0: + resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==} + engines: {node: '>=4'} + reflect-metadata@0.2.2: resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} @@ -3032,6 +3139,9 @@ packages: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} + standard-as-callback@2.1.0: + resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} + statuses@2.0.2: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} @@ -3998,6 +4108,8 @@ snapshots: optionalDependencies: '@types/node': 22.20.1 + '@ioredis/commands@1.10.0': {} + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -4240,6 +4352,24 @@ snapshots: '@microsoft/tsdoc@0.16.0': {} + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4': + optional: true + + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.4': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.4': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.4': + optional: true + + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4': + optional: true + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: '@emnapi/core': 1.10.0 @@ -4253,6 +4383,20 @@ snapshots: axios: 1.18.1 rxjs: 7.8.2 + '@nestjs/bull-shared@11.0.4(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.28)': + dependencies: + '@nestjs/common': 11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.28)(reflect-metadata@0.2.2)(rxjs@7.8.2) + tslib: 2.8.1 + + '@nestjs/bullmq@11.0.4(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.28)(bullmq@5.81.3)': + dependencies: + '@nestjs/bull-shared': 11.0.4(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.28) + '@nestjs/common': 11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.28)(reflect-metadata@0.2.2)(rxjs@7.8.2) + bullmq: 5.81.3 + tslib: 2.8.1 + '@nestjs/cache-manager@3.1.3(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.28)(cache-manager@7.2.9)(keyv@5.6.0)(rxjs@7.8.2)': dependencies: '@nestjs/common': 11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -4400,13 +4544,13 @@ snapshots: optionalDependencies: '@nestjs/platform-express': 11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.28) - '@nestjs/typeorm@11.0.3(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.28)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@1.1.0(pg@8.22.0)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.9.3)))': + '@nestjs/typeorm@11.0.3(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.28)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@1.1.0(ioredis@5.11.1)(pg@8.22.0)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.9.3)))': dependencies: '@nestjs/common': 11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/core': 11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.28)(reflect-metadata@0.2.2)(rxjs@7.8.2) reflect-metadata: 0.2.2 rxjs: 7.8.2 - typeorm: 1.1.0(pg@8.22.0)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.9.3)) + typeorm: 1.1.0(ioredis@5.11.1)(pg@8.22.0)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.9.3)) '@noble/hashes@1.8.0': {} @@ -5085,6 +5229,17 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 + bullmq@5.81.3: + dependencies: + cron-parser: 4.9.0 + ioredis: 5.11.1 + msgpackr: 2.0.5 + node-abort-controller: 3.1.1 + semver: 7.8.5 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + busboy@1.6.0: dependencies: streamsearch: 1.1.0 @@ -5169,6 +5324,8 @@ snapshots: clone@1.0.4: {} + cluster-key-slot@1.1.1: {} + cluster-key-slot@1.1.2: {} co@4.6.0: {} @@ -5235,6 +5392,10 @@ snapshots: create-require@1.1.1: {} + cron-parser@4.9.0: + dependencies: + luxon: 3.7.2 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -5259,8 +5420,13 @@ snapshots: delayed-stream@1.0.0: {} + denque@2.1.0: {} + depd@2.0.0: {} + detect-libc@2.1.2: + optional: true + detect-newline@3.1.0: {} dezalgo@1.0.4: @@ -5748,6 +5914,18 @@ snapshots: inherits@2.0.4: {} + ioredis@5.11.1: + dependencies: + '@ioredis/commands': 1.10.0 + cluster-key-slot: 1.1.1 + debug: 4.4.3 + denque: 2.1.0 + redis-errors: 1.2.0 + redis-parser: 3.0.0 + standard-as-callback: 2.1.0 + transitivePeerDependencies: + - supports-color + ipaddr.js@1.9.1: {} is-arrayish@0.2.1: {} @@ -6264,6 +6442,8 @@ snapshots: dependencies: yallist: 3.1.1 + luxon@3.7.2: {} + magic-string@0.30.17: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -6328,6 +6508,22 @@ snapshots: ms@2.1.3: {} + msgpackr-extract@3.0.4: + dependencies: + node-gyp-build-optional-packages: 5.2.2 + optionalDependencies: + '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.4 + '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.4 + '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.4 + '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.4 + '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.4 + '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.4 + optional: true + + msgpackr@2.0.5: + optionalDependencies: + msgpackr-extract: 3.0.4 + multer@2.2.0: dependencies: append-field: 1.0.0 @@ -6353,6 +6549,11 @@ snapshots: dependencies: lodash: 4.18.1 + node-gyp-build-optional-packages@5.2.2: + dependencies: + detect-libc: 2.1.2 + optional: true + node-gyp-build@4.8.4: {} node-int64@0.4.0: {} @@ -6583,6 +6784,12 @@ snapshots: readdirp@4.1.2: {} + redis-errors@1.2.0: {} + + redis-parser@3.0.0: + dependencies: + redis-errors: 1.2.0 + reflect-metadata@0.2.2: {} require-directory@2.1.1: {} @@ -6734,6 +6941,8 @@ snapshots: dependencies: escape-string-regexp: 2.0.0 + standard-as-callback@2.1.0: {} + statuses@2.0.2: {} streamsearch@1.1.0: {} @@ -6951,7 +7160,7 @@ snapshots: typedarray@0.0.6: {} - typeorm@1.1.0(pg@8.22.0)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.9.3)): + typeorm@1.1.0(ioredis@5.11.1)(pg@8.22.0)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.9.3)): dependencies: '@sqltools/formatter': 1.2.5 ansis: 4.3.1 @@ -6964,6 +7173,7 @@ snapshots: tslib: 2.8.1 yargs: 18.0.0 optionalDependencies: + ioredis: 5.11.1 pg: 8.22.0 ts-node: 10.9.2(@types/node@22.20.1)(typescript@5.9.3) transitivePeerDependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index cb54f9b..ce72cdd 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,4 +1,5 @@ allowBuilds: '@scarf/scarf': false bcrypt: false + msgpackr-extract: false unrs-resolver: false diff --git a/src/app.module.ts b/src/app.module.ts index a4cefb9..da4a7c4 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -12,6 +12,7 @@ import { AttachmentModule } from './modules/attachment/attachment.module'; import { ReplyModule } from './modules/reply/reply.module'; import { CacheModule } from '@nestjs/cache-manager'; import KeyvRedis from '@keyv/redis'; +import { BullModule } from '@nestjs/bullmq'; @Module({ imports: [ @@ -26,6 +27,16 @@ import KeyvRedis from '@keyv/redis'; stores: [new KeyvRedis(config.getOrThrow('REDIS_URL'))], }), }), + BullModule.forRootAsync({ + inject: [ConfigService], + useFactory: (configService: ConfigService) => ({ + connection: { + host: configService.getOrThrow('REDIS_HOST'), + port: configService.getOrThrow('REDIS_PORT'), + // password: configService.get('REDIS_PASSWORD'), + }, + }), + }), TypeOrmModule.forRootAsync(databaseConfig()), HttpModule.register({ global: true, diff --git a/src/config/sms.config.ts b/src/config/sms.config.ts index 0a4fd52..ada1c0c 100644 --- a/src/config/sms.config.ts +++ b/src/config/sms.config.ts @@ -1,7 +1,7 @@ import { FactoryProvider } from "@nestjs/common"; import { ConfigService } from "@nestjs/config"; -import { SMS_CONFIG } from "src/modules/utils/constants"; +import { SMS_CONFIG } from "src/modules/notifications/constants"; export const smsConfigsProvider: FactoryProvider = { provide: SMS_CONFIG, diff --git a/src/modules/auth/auth.module.ts b/src/modules/auth/auth.module.ts index d16bb50..4ffbf02 100644 --- a/src/modules/auth/auth.module.ts +++ b/src/modules/auth/auth.module.ts @@ -2,25 +2,21 @@ import { Module } from '@nestjs/common'; import { JwtModule } from '@nestjs/jwt'; import { AuthController } from './auth.controller'; import { AuthService } from './auth.service'; -import { UtilsModule } from '../utils/utils.module'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { OTP } from './entities/otp.entity'; -import { OtpRepository } from './repositories/otp.repository'; import { UsersModule } from 'src/modules/users/users.module'; import { PassportModule } from '@nestjs/passport'; import { JwtStrategy } from './strategies/jwt.strategy'; import { OtpService } from './otp.service'; +import { NotificationModule } from '../notifications/notification.module'; @Module({ imports: [ - TypeOrmModule.forFeature([OTP]), PassportModule, JwtModule.register({}), - UtilsModule, UsersModule, + NotificationModule, ], controllers: [AuthController], - providers: [AuthService, OtpService, OtpRepository, JwtStrategy], + providers: [AuthService, OtpService, JwtStrategy], exports: [JwtModule, PassportModule], }) -export class AuthModule {} +export class AuthModule {} \ No newline at end of file diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts index 9810e61..b46e263 100644 --- a/src/modules/auth/auth.service.ts +++ b/src/modules/auth/auth.service.ts @@ -1,27 +1,23 @@ import { BadRequestException, Inject, Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import type { ISmsConfigs } from 'src/config/sms.config'; -import { SMS_CONFIG } from '../utils/constants'; -import { SmsService } from '../utils/providers/sms.service'; -import { OtpRepository } from './repositories/otp.repository'; -import { OtpStatus } from './enums/otp-status.enum'; +import { SMS_CONFIG } from '../notifications/constants'; import { VerifyOtpDto } from './DTO/verify-otp.dto'; -import { OTPMessages, UserMessages } from 'src/common/enums/messages.enum'; +import { UserMessages } from 'src/common/enums/messages.enum'; import { LoginCredentialDto } from './DTO/login-credential.dto'; import { UsersService } from 'src/modules/users/users.service'; import * as bcrypt from 'bcrypt'; import { JwtService } from '@nestjs/jwt'; import ms from 'ms'; import { OtpService } from './otp.service'; -import { RoleType } from '../users/enums/user-role.enum'; import { SendOtpDto } from './DTO/send-otp.dto'; -import { string } from 'joi'; +import { SmsProducer } from '../notifications/producers/sms.producer'; @Injectable() export class AuthService { constructor( @Inject(SMS_CONFIG) private smsConfig: ISmsConfigs, - private readonly smsService: SmsService, + private readonly smsProducer: SmsProducer, private readonly configService: ConfigService, private readonly userService: UsersService, private readonly jwtService: JwtService, @@ -35,7 +31,7 @@ export class AuthService { }); try { - await this.smsService.sendSms({ + await this.smsProducer.sendSms({ Mobile: dto.phoneNumber, TemplateId: this.smsConfig.SMS_PATTERN_OTP, Parameters: [ @@ -61,7 +57,6 @@ export class AuthService { { phoneNumber: dto.phoneNumber, role: dto.role }, dto.code, ); - if (dto.role) console.log('VERIFIED OTP FOR ROLE: ', dto.role); } async checkLoginCredentials(credential: LoginCredentialDto): Promise { diff --git a/src/modules/auth/entities/otp.entity.ts b/src/modules/auth/entities/otp.entity.ts deleted file mode 100644 index 104a8cd..0000000 --- a/src/modules/auth/entities/otp.entity.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Column, Entity } from 'typeorm'; -import { BaseEntity } from 'src/common/entities/base.entity'; -import { OtpStatus } from '../enums/otp-status.enum'; - -@Entity('otps') -export class OTP extends BaseEntity { - @Column({ type: 'varchar', length: 6 }) - code: string; - - @Column({ type: 'timestamptz' }) - expirationDate: Date; - - @Column({type: 'enum', enum: OtpStatus, default: OtpStatus.PENDING}) - status: OtpStatus; - - @Column({ type: 'smallint', default: 0 }) - numberOfTries: number; - - @Column({ - length: 11, - }) - phoneNumber: string; -} diff --git a/src/modules/auth/repositories/otp.repository.ts b/src/modules/auth/repositories/otp.repository.ts deleted file mode 100644 index 08c2f87..0000000 --- a/src/modules/auth/repositories/otp.repository.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Repository } from 'typeorm'; -import { OTP } from '../entities/otp.entity'; -import { InjectRepository } from '@nestjs/typeorm'; - -export class OtpRepository extends Repository { - constructor(@InjectRepository(OTP) otpRepository: Repository) { - super( - otpRepository.target, - otpRepository.manager, - otpRepository.queryRunner, - ); - } -} diff --git a/src/modules/notifications/constants.ts b/src/modules/notifications/constants.ts new file mode 100644 index 0000000..d80c184 --- /dev/null +++ b/src/modules/notifications/constants.ts @@ -0,0 +1,2 @@ +export const SMS_CONFIG = "SMS_CONFIG"; +export const SMS_QUEUE = 'sms'; \ No newline at end of file diff --git a/src/modules/utils/interfaces/ISms.ts b/src/modules/notifications/interfaces/ISms.ts similarity index 100% rename from src/modules/utils/interfaces/ISms.ts rename to src/modules/notifications/interfaces/ISms.ts diff --git a/src/modules/notifications/notification.module.ts b/src/modules/notifications/notification.module.ts new file mode 100644 index 0000000..c458ecf --- /dev/null +++ b/src/modules/notifications/notification.module.ts @@ -0,0 +1,23 @@ +import { Module } from '@nestjs/common'; +import { BullModule } from '@nestjs/bullmq'; +import { SmsService } from './providers/sms.service'; +import { SmsProducer } from './producers/sms.producer'; +import { SmsProcessor } from './processors/sms.processor'; +import { SMS_CONFIG, SMS_QUEUE } from './constants'; +import { smsConfigsProvider } from 'src/config/sms.config'; +import { HttpModule } from '@nestjs/axios'; + +@Module({ + imports: [ + HttpModule, + BullModule.registerQueue({ + name: SMS_QUEUE, + }), + ], + providers: [SmsService, smsConfigsProvider, SmsProducer, SmsProcessor], + exports: [SmsProducer, SMS_CONFIG], +}) +export class NotificationModule {} + +export { SMS_QUEUE }; + diff --git a/src/modules/notifications/processors/sms.processor.ts b/src/modules/notifications/processors/sms.processor.ts new file mode 100644 index 0000000..f8e0f95 --- /dev/null +++ b/src/modules/notifications/processors/sms.processor.ts @@ -0,0 +1,22 @@ +import { Processor, WorkerHost } from '@nestjs/bullmq'; +import { Job } from 'bullmq'; +import { Logger } from '@nestjs/common'; +import { SMS_QUEUE } from '../constants'; +import { SmsService } from '../providers/sms.service'; +import { SmsRequest } from '../interfaces/ISms'; + +@Processor(SMS_QUEUE) +export class SmsProcessor extends WorkerHost { + private readonly logger = new Logger(SmsProcessor.name); + + constructor(private readonly smsService: SmsService) { + super(); + } + + async process(job: Job): Promise { + this.logger.log( + `Processing SMS job ${job.id} (attempt ${job.attemptsMade + 1})`, + ); + await this.smsService.sendSms(job.data); + } +} diff --git a/src/modules/notifications/producers/sms.producer.ts b/src/modules/notifications/producers/sms.producer.ts new file mode 100644 index 0000000..830bee3 --- /dev/null +++ b/src/modules/notifications/producers/sms.producer.ts @@ -0,0 +1,19 @@ +import { Injectable } from '@nestjs/common'; +import { InjectQueue } from '@nestjs/bullmq'; +import { Queue } from 'bullmq'; +import { SMS_QUEUE } from '../constants'; +import { SmsRequest } from '../interfaces/ISms'; + +@Injectable() +export class SmsProducer { + constructor(@InjectQueue(SMS_QUEUE) private readonly smsQueue: Queue) {} + + async sendSms(body: SmsRequest) { + return this.smsQueue.add('send-sms', body, { + attempts: 3, + backoff: { type: 'exponential', delay: 2000 }, + removeOnComplete: true, + removeOnFail: false, + }); + } +} \ No newline at end of file diff --git a/src/modules/utils/providers/sms.service.ts b/src/modules/notifications/providers/sms.service.ts similarity index 98% rename from src/modules/utils/providers/sms.service.ts rename to src/modules/notifications/providers/sms.service.ts index 0d969f9..3a3a401 100644 --- a/src/modules/utils/providers/sms.service.ts +++ b/src/modules/notifications/providers/sms.service.ts @@ -1,5 +1,4 @@ import { - BadGatewayException, Inject, Injectable, InternalServerErrorException, diff --git a/src/modules/users/DTO/create-user.dto.ts b/src/modules/users/DTO/create-user.dto.ts index 763e8c5..1c418a7 100644 --- a/src/modules/users/DTO/create-user.dto.ts +++ b/src/modules/users/DTO/create-user.dto.ts @@ -1,5 +1,4 @@ import { ApiProperty } from '@nestjs/swagger'; -import { Exclude } from 'class-transformer'; import { IsEmail, IsNotEmpty, diff --git a/src/modules/utils/constants.ts b/src/modules/utils/constants.ts deleted file mode 100644 index 9ffb806..0000000 --- a/src/modules/utils/constants.ts +++ /dev/null @@ -1 +0,0 @@ -export const SMS_CONFIG = "SMS_CONFIG"; \ No newline at end of file diff --git a/src/modules/utils/utils.module.ts b/src/modules/utils/utils.module.ts deleted file mode 100644 index 485d116..0000000 --- a/src/modules/utils/utils.module.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Module } from '@nestjs/common'; -import { SmsService } from './providers/sms.service'; -import { smsConfigsProvider } from 'src/config/sms.config'; -import { SMS_CONFIG } from './constants'; - -@Module({ - providers: [SmsService, smsConfigsProvider], - exports: [SMS_CONFIG, SmsService], -}) -export class UtilsModule {}