From f6763939e20ffdaa6db100e13ed4260abc714f1e Mon Sep 17 00:00:00 2001 From: isra el Date: Mon, 24 Mar 2025 03:39:29 +0300 Subject: [PATCH] feat(api): implement optional job queue for sending sms with delay and scheduling --- api/.env.example | 4 + api/package.json | 6 + api/pnpm-lock.yaml | 536 +++++++++++++++---- api/src/app.module.ts | 43 +- api/src/gateway/gateway.module.ts | 21 +- api/src/gateway/gateway.service.ts | 212 ++++++-- api/src/gateway/queue/sms-queue.processor.ts | 102 ++++ api/src/gateway/queue/sms-queue.service.ts | 66 +++ api/src/gateway/schemas/sms-batch.schema.ts | 15 + api/src/gateway/schemas/sms.schema.ts | 7 +- 10 files changed, 851 insertions(+), 161 deletions(-) create mode 100644 api/src/gateway/queue/sms-queue.processor.ts create mode 100644 api/src/gateway/queue/sms-queue.service.ts diff --git a/api/.env.example b/api/.env.example index 44ee220..97f3fa2 100644 --- a/api/.env.example +++ b/api/.env.example @@ -19,3 +19,7 @@ MAIL_USER= MAIL_PASS= MAIL_FROM= MAIL_REPLY_TO=textbee.dev@gmail.com + +# SMS Queue Configuration +USE_SMS_QUEUE=false +REDIS_URL=redis://localhost:6379 # if queue is enabled, redis url is required diff --git a/api/package.json b/api/package.json index 06653a9..ebd502c 100644 --- a/api/package.json +++ b/api/package.json @@ -21,9 +21,12 @@ }, "dependencies": { "@nest-modules/mailer": "^1.3.22", + "@nestjs/bull": "^11.0.2", "@nestjs/common": "^10.4.5", + "@nestjs/config": "^4.0.1", "@nestjs/core": "^10.4.5", "@nestjs/jwt": "^10.2.0", + "@nestjs/mapped-types": "^2.1.0", "@nestjs/mongoose": "^10.0.10", "@nestjs/passport": "^10.0.3", "@nestjs/platform-express": "^10.4.5", @@ -33,10 +36,13 @@ "@polar-sh/sdk": "^0.30.0", "axios": "^1.8.2", "bcryptjs": "^2.4.3", + "bull": "^4.16.5", + "class-validator": "^0.14.1", "dotenv": "^16.4.5", "express": "^4.21.2", "firebase-admin": "^12.6.0", "handlebars": "^4.7.8", + "ioredis": "^5.6.0", "mongoose": "^8.12.1", "nodemailer": "^6.10.0", "passport": "^0.7.0", diff --git a/api/pnpm-lock.yaml b/api/pnpm-lock.yaml index 5d1735c..db9c705 100644 --- a/api/pnpm-lock.yaml +++ b/api/pnpm-lock.yaml @@ -10,34 +10,43 @@ importers: dependencies: '@nest-modules/mailer': specifier: ^1.3.22 - version: 1.3.22(@nestjs/common@10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.5)(nodemailer@6.10.0) + version: 1.3.22(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.5)(nodemailer@6.10.0) + '@nestjs/bull': + specifier: ^11.0.2 + version: 11.0.2(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.5)(bull@4.16.5) '@nestjs/common': specifier: ^10.4.5 - version: 10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1) + version: 10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/config': + specifier: ^4.0.1 + version: 4.0.1(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(rxjs@7.8.1) '@nestjs/core': specifier: ^10.4.5 - version: 10.4.5(@nestjs/common@10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.5)(reflect-metadata@0.2.2)(rxjs@7.8.1) + version: 10.4.5(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.5)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/jwt': specifier: ^10.2.0 - version: 10.2.0(@nestjs/common@10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1)) + version: 10.2.0(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)) + '@nestjs/mapped-types': + specifier: ^2.1.0 + version: 2.1.0(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-validator@0.14.1)(reflect-metadata@0.2.2) '@nestjs/mongoose': specifier: ^10.0.10 - version: 10.0.10(@nestjs/common@10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.5)(mongoose@8.12.1(@aws-sdk/credential-providers@3.675.0(@aws-sdk/client-sso-oidc@3.675.0(@aws-sdk/client-sts@3.675.0)))(socks@2.8.3))(rxjs@7.8.1) + version: 10.0.10(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.5)(mongoose@8.12.1(@aws-sdk/credential-providers@3.675.0(@aws-sdk/client-sso-oidc@3.675.0(@aws-sdk/client-sts@3.675.0)))(socks@2.8.3))(rxjs@7.8.1) '@nestjs/passport': specifier: ^10.0.3 - version: 10.0.3(@nestjs/common@10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1))(passport@0.7.0) + version: 10.0.3(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(passport@0.7.0) '@nestjs/platform-express': specifier: ^10.4.5 - version: 10.4.5(@nestjs/common@10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.5) + version: 10.4.5(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.5) '@nestjs/schedule': specifier: ^4.1.1 - version: 4.1.1(@nestjs/common@10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.5) + version: 4.1.1(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.5) '@nestjs/swagger': specifier: ^7.4.2 - version: 7.4.2(@nestjs/common@10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.5)(reflect-metadata@0.2.2) + version: 7.4.2(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.5)(class-validator@0.14.1)(reflect-metadata@0.2.2) '@nestjs/throttler': specifier: ^6.2.1 - version: 6.2.1(@nestjs/common@10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.5)(reflect-metadata@0.2.2) + version: 6.2.1(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.5)(reflect-metadata@0.2.2) '@polar-sh/sdk': specifier: ^0.30.0 version: 0.30.0(zod@3.24.1) @@ -47,6 +56,12 @@ importers: bcryptjs: specifier: ^2.4.3 version: 2.4.3 + bull: + specifier: ^4.16.5 + version: 4.16.5 + class-validator: + specifier: ^0.14.1 + version: 0.14.1 dotenv: specifier: ^16.4.5 version: 16.4.5 @@ -59,6 +74,9 @@ importers: handlebars: specifier: ^4.7.8 version: 4.7.8 + ioredis: + specifier: ^5.6.0 + version: 5.6.0 mongoose: specifier: ^8.12.1 version: 8.12.1(@aws-sdk/credential-providers@3.675.0(@aws-sdk/client-sso-oidc@3.675.0(@aws-sdk/client-sts@3.675.0)))(socks@2.8.3) @@ -95,7 +113,7 @@ importers: version: 10.2.2(chokidar@3.6.0)(typescript@5.6.3) '@nestjs/testing': specifier: ^10.4.5 - version: 10.4.5(@nestjs/common@10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.5)(@nestjs/platform-express@10.4.5) + version: 10.4.5(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.5)(@nestjs/platform-express@10.4.5) '@types/express': specifier: ^5.0.0 version: 5.0.0 @@ -605,6 +623,9 @@ packages: resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} engines: {node: '>=18.18'} + '@ioredis/commands@1.2.0': + resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==} + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -727,6 +748,36 @@ packages: '@mongodb-js/saslprep@1.1.9': resolution: {integrity: sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==} + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': + resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==} + cpu: [arm64] + os: [darwin] + + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3': + resolution: {integrity: sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==} + cpu: [x64] + os: [darwin] + + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3': + resolution: {integrity: sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==} + cpu: [arm64] + os: [linux] + + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3': + resolution: {integrity: sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==} + cpu: [arm] + os: [linux] + + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3': + resolution: {integrity: sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==} + cpu: [x64] + os: [linux] + + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': + resolution: {integrity: sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==} + cpu: [x64] + os: [win32] + '@nest-modules/mailer@1.3.22': resolution: {integrity: sha512-pWwM4RbLbyZvQ+Y55fMkzbFCGaJRyrQkZA6jxuRq+oVV/uT8UUtZc5bi4+w8rOPoLj7bqYvUAcvmtyXeMK9HbA==} peerDependencies: @@ -734,6 +785,19 @@ packages: '@nestjs/core': ^6.7.0 || ^7.0.0 nodemailer: ^6.0.0 + '@nestjs/bull-shared@11.0.2': + resolution: {integrity: sha512-dFlttJvBqIFD6M8JVFbkrR4Feb39OTAJPJpFVILU50NOJCM4qziRw3dSNG84Q3v+7/M6xUGMFdZRRGvBBKxoSA==} + peerDependencies: + '@nestjs/common': ^10.0.0 || ^11.0.0 + '@nestjs/core': ^10.0.0 || ^11.0.0 + + '@nestjs/bull@11.0.2': + resolution: {integrity: sha512-RjyP9JZUuLmMhmq1TMNIZqolkAd14az1jyXMMVki+C9dYvaMjWzBSwcZAtKs9Pk15Rm7qN1xn3R11aMV2Xv4gg==} + 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 + '@nestjs/cli@10.4.5': resolution: {integrity: sha512-FP7Rh13u8aJbHe+zZ7hM0CC4785g9Pw4lz4r2TTgRtf0zTxSWMkJaPEwyjX8SK9oWK2GsYxl+fKpwVZNbmnj9A==} engines: {node: '>= 16.14'} @@ -760,6 +824,12 @@ packages: class-validator: optional: true + '@nestjs/config@4.0.1': + resolution: {integrity: sha512-0hr6lKS//Wf8A6VcV69ts8uD0fke6jtmmmXSxzvwAzOM/HEXEKYEp21nRU+cpYxlYqm7Khb0oTOoVuDGk+AWUw==} + peerDependencies: + '@nestjs/common': ^10.0.0 || ^11.0.0 + rxjs: ^7.1.0 + '@nestjs/core@10.4.5': resolution: {integrity: sha512-wk0KJ+6tuidqAdeemsQ40BCp1BgMsSuSLG577aqXLxXYoa8FQYPrdxoSzd05znYLwJYM55fisZWb3FLF9HT2qw==} peerDependencies: @@ -795,6 +865,19 @@ packages: class-validator: optional: true + '@nestjs/mapped-types@2.1.0': + resolution: {integrity: sha512-W+n+rM69XsFdwORF11UqJahn4J3xi4g/ZEOlJNL6KoW5ygWSmBB2p0S2BZ4FQeS/NDH72e6xIcu35SfJnE8bXw==} + 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 + '@nestjs/mongoose@10.0.10': resolution: {integrity: sha512-3Ff60ock8nwlAJC823TG91Qy+Qc6av+ddIb6n6wlFsTK0akDF/aTcagX8cF8uI8mWxCWjEwEsgv99vo6p0yJ+w==} peerDependencies: @@ -1257,6 +1340,9 @@ packages: '@types/tough-cookie@4.0.5': resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + '@types/validator@13.12.2': + resolution: {integrity: sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA==} + '@types/webidl-conversions@7.0.3': resolution: {integrity: sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==} @@ -1627,6 +1713,10 @@ packages: buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + bull@4.16.5: + resolution: {integrity: sha512-lDsx2BzkKe7gkCYiT5Acj02DpTwDznl/VNN7Psn7M3USPG7Vs/BaClZJJTAG+ufAR9++N1/NiUTdaFBWDIl5TQ==} + engines: {node: '>=12'} + busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} @@ -1703,6 +1793,9 @@ packages: cjs-module-lexer@1.4.1: resolution: {integrity: sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==} + class-validator@0.14.1: + resolution: {integrity: sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==} + clean-css@4.2.4: resolution: {integrity: sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==} engines: {node: '>= 4.0'} @@ -1738,6 +1831,10 @@ packages: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} + cluster-key-slot@1.1.2: + resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} + engines: {node: '>=0.10.0'} + co@4.6.0: resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} @@ -1838,6 +1935,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'} + cron@3.1.7: resolution: {integrity: sha512-tlBg7ARsAMQLzgwqVxy8AZl/qlTc5nibqYwtNGoCrd+cV+ugI+tvZC1oT/8dFH8W455YrywGykx/KMmAqOr7Jw==} @@ -1920,6 +2021,10 @@ 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'} @@ -1928,6 +2033,10 @@ packages: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + detect-libc@2.0.3: + resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} + engines: {node: '>=8'} + detect-newline@3.1.0: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} @@ -1961,10 +2070,18 @@ packages: domutils@1.7.0: resolution: {integrity: sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==} + dotenv-expand@12.0.1: + resolution: {integrity: sha512-LaKRbou8gt0RNID/9RoI+J2rvXsBRPMV7p+ElHlPhcSARbCPDYcYG2s1TIzAfWv4YSgyY5taidWzzs31lNV3yQ==} + engines: {node: '>=12'} + dotenv@16.4.5: resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} engines: {node: '>=12'} + dotenv@16.4.7: + resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} + engines: {node: '>=12'} + duplexify@4.1.3: resolution: {integrity: sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==} @@ -2357,6 +2474,10 @@ packages: resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} engines: {node: '>=8.0.0'} + get-port@5.1.1: + resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==} + engines: {node: '>=8'} + get-stream@6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} @@ -2545,6 +2666,10 @@ packages: resolution: {integrity: sha512-vI2w4zl/mDluHt9YEQ/543VTCwPKWiHzKtm9dM2V0NdFcqEexDAjUHzO1oA60HRNaVifGXXM1tRRNluLVHa0Kg==} engines: {node: '>=18'} + ioredis@5.6.0: + resolution: {integrity: sha512-tBZlIIWbndeWBWCXWZiqtOF/yxf6yZX3tAlTJ7nfo5jhd6dctNxF7QnYlZLZ1a0o0pDoen7CgZqO+zjNaFbJAg==} + engines: {node: '>=12.22.0'} + ip-address@9.0.5: resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} engines: {node: '>= 12'} @@ -2908,6 +3033,9 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + libphonenumber-js@1.12.6: + resolution: {integrity: sha512-PJiS4ETaUfCOFLpmtKzAbqZQjCCKVu2OhTV4SVNNE7c2nu/dACvtCqj4L0i/KWNnIgRv7yrILvBj5Lonv5Ncxw==} + limiter@1.1.5: resolution: {integrity: sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==} @@ -2956,6 +3084,9 @@ packages: lodash.includes@4.3.0: resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + lodash.isarguments@3.1.0: + resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} + lodash.isboolean@3.0.3: resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} @@ -3177,6 +3308,13 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + msgpackr-extract@3.0.3: + resolution: {integrity: sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==} + hasBin: true + + msgpackr@1.11.2: + resolution: {integrity: sha512-F9UngXRlPyWCDEASDpTf6c9uNhGPTqnTeLVt7bN+bU1eajoR/8V9ys2BRaV5C/e5ihE6sJ9uPIKaYt6bFuO32g==} + multer@1.4.4-lts.1: resolution: {integrity: sha512-WeSGziVj6+Z2/MwQo3GvqzgR+9Uc+qt8SwHKh3gvNPiISKfsMfG4SvCOFYlxxgkXt7yIV2i1yczehm0EOKIxIg==} engines: {node: '>= 6.0.0'} @@ -3221,6 +3359,10 @@ packages: resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} engines: {node: '>= 6.13.0'} + node-gyp-build-optional-packages@5.2.2: + resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==} + hasBin: true + node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} @@ -3528,6 +3670,14 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.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==} @@ -3727,6 +3877,9 @@ packages: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} + standard-as-callback@2.1.0: + resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} + standardwebhooks@1.0.0: resolution: {integrity: sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==} @@ -3991,6 +4144,9 @@ packages: tslib@2.8.0: resolution: {integrity: sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + type-check@0.3.2: resolution: {integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==} engines: {node: '>= 0.8.0'} @@ -4091,6 +4247,10 @@ packages: resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} engines: {node: '>=10.12.0'} + validator@13.12.0: + resolution: {integrity: sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==} + engines: {node: '>= 0.10'} + vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} @@ -4298,26 +4458,26 @@ snapshots: '@aws-sdk/types': 3.667.0 '@aws-sdk/util-locate-window': 3.568.0 '@smithy/util-utf8': 2.3.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@aws-crypto/sha256-js@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 '@aws-sdk/types': 3.667.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@aws-crypto/supports-web-crypto@5.2.0': dependencies: - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@aws-crypto/util@5.2.0': dependencies: '@aws-sdk/types': 3.667.0 '@smithy/util-utf8': 2.3.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@aws-sdk/client-cognito-identity@3.675.0': @@ -4362,7 +4522,7 @@ snapshots: '@smithy/util-middleware': 3.0.7 '@smithy/util-retry': 3.0.7 '@smithy/util-utf8': 3.0.0 - tslib: 2.8.0 + tslib: 2.8.1 transitivePeerDependencies: - aws-crt optional: true @@ -4408,7 +4568,7 @@ snapshots: '@smithy/util-middleware': 3.0.7 '@smithy/util-retry': 3.0.7 '@smithy/util-utf8': 3.0.0 - tslib: 2.8.0 + tslib: 2.8.1 transitivePeerDependencies: - aws-crt optional: true @@ -4452,7 +4612,7 @@ snapshots: '@smithy/util-middleware': 3.0.7 '@smithy/util-retry': 3.0.7 '@smithy/util-utf8': 3.0.0 - tslib: 2.8.0 + tslib: 2.8.1 transitivePeerDependencies: - aws-crt optional: true @@ -4498,7 +4658,7 @@ snapshots: '@smithy/util-middleware': 3.0.7 '@smithy/util-retry': 3.0.7 '@smithy/util-utf8': 3.0.0 - tslib: 2.8.0 + tslib: 2.8.1 transitivePeerDependencies: - aws-crt optional: true @@ -4515,7 +4675,7 @@ snapshots: '@smithy/types': 3.5.0 '@smithy/util-middleware': 3.0.7 fast-xml-parser: 4.4.1 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@aws-sdk/credential-provider-cognito-identity@3.675.0': @@ -4524,7 +4684,7 @@ snapshots: '@aws-sdk/types': 3.667.0 '@smithy/property-provider': 3.1.7 '@smithy/types': 3.5.0 - tslib: 2.8.0 + tslib: 2.8.1 transitivePeerDependencies: - aws-crt optional: true @@ -4535,7 +4695,7 @@ snapshots: '@aws-sdk/types': 3.667.0 '@smithy/property-provider': 3.1.7 '@smithy/types': 3.5.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@aws-sdk/credential-provider-http@3.667.0': @@ -4549,7 +4709,7 @@ snapshots: '@smithy/smithy-client': 3.4.0 '@smithy/types': 3.5.0 '@smithy/util-stream': 3.1.9 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@aws-sdk/credential-provider-ini@3.675.0(@aws-sdk/client-sso-oidc@3.675.0(@aws-sdk/client-sts@3.675.0))(@aws-sdk/client-sts@3.675.0)': @@ -4566,7 +4726,7 @@ snapshots: '@smithy/property-provider': 3.1.7 '@smithy/shared-ini-file-loader': 3.1.8 '@smithy/types': 3.5.0 - tslib: 2.8.0 + tslib: 2.8.1 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - aws-crt @@ -4585,7 +4745,7 @@ snapshots: '@smithy/property-provider': 3.1.7 '@smithy/shared-ini-file-loader': 3.1.8 '@smithy/types': 3.5.0 - tslib: 2.8.0 + tslib: 2.8.1 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - '@aws-sdk/client-sts' @@ -4599,7 +4759,7 @@ snapshots: '@smithy/property-provider': 3.1.7 '@smithy/shared-ini-file-loader': 3.1.8 '@smithy/types': 3.5.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@aws-sdk/credential-provider-sso@3.675.0(@aws-sdk/client-sso-oidc@3.675.0(@aws-sdk/client-sts@3.675.0))': @@ -4611,7 +4771,7 @@ snapshots: '@smithy/property-provider': 3.1.7 '@smithy/shared-ini-file-loader': 3.1.8 '@smithy/types': 3.5.0 - tslib: 2.8.0 + tslib: 2.8.1 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - aws-crt @@ -4624,7 +4784,7 @@ snapshots: '@aws-sdk/types': 3.667.0 '@smithy/property-provider': 3.1.7 '@smithy/types': 3.5.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@aws-sdk/credential-providers@3.675.0(@aws-sdk/client-sso-oidc@3.675.0(@aws-sdk/client-sts@3.675.0))': @@ -4645,7 +4805,7 @@ snapshots: '@smithy/credential-provider-imds': 3.2.4 '@smithy/property-provider': 3.1.7 '@smithy/types': 3.5.0 - tslib: 2.8.0 + tslib: 2.8.1 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - aws-crt @@ -4656,14 +4816,14 @@ snapshots: '@aws-sdk/types': 3.667.0 '@smithy/protocol-http': 4.1.4 '@smithy/types': 3.5.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@aws-sdk/middleware-logger@3.667.0': dependencies: '@aws-sdk/types': 3.667.0 '@smithy/types': 3.5.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@aws-sdk/middleware-recursion-detection@3.667.0': @@ -4671,7 +4831,7 @@ snapshots: '@aws-sdk/types': 3.667.0 '@smithy/protocol-http': 4.1.4 '@smithy/types': 3.5.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@aws-sdk/middleware-user-agent@3.669.0': @@ -4682,7 +4842,7 @@ snapshots: '@smithy/core': 2.4.8 '@smithy/protocol-http': 4.1.4 '@smithy/types': 3.5.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@aws-sdk/region-config-resolver@3.667.0': @@ -4692,7 +4852,7 @@ snapshots: '@smithy/types': 3.5.0 '@smithy/util-config-provider': 3.0.0 '@smithy/util-middleware': 3.0.7 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@aws-sdk/token-providers@3.667.0(@aws-sdk/client-sso-oidc@3.675.0(@aws-sdk/client-sts@3.675.0))': @@ -4702,13 +4862,13 @@ snapshots: '@smithy/property-provider': 3.1.7 '@smithy/shared-ini-file-loader': 3.1.8 '@smithy/types': 3.5.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@aws-sdk/types@3.667.0': dependencies: '@smithy/types': 3.5.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@aws-sdk/util-endpoints@3.667.0': @@ -4716,12 +4876,12 @@ snapshots: '@aws-sdk/types': 3.667.0 '@smithy/types': 3.5.0 '@smithy/util-endpoints': 2.1.3 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@aws-sdk/util-locate-window@3.568.0': dependencies: - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@aws-sdk/util-user-agent-browser@3.675.0': @@ -4729,7 +4889,7 @@ snapshots: '@aws-sdk/types': 3.667.0 '@smithy/types': 3.5.0 bowser: 2.11.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@aws-sdk/util-user-agent-node@3.669.0': @@ -4738,7 +4898,7 @@ snapshots: '@aws-sdk/types': 3.667.0 '@smithy/node-config-provider': 3.1.8 '@smithy/types': 3.5.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@babel/code-frame@7.25.7': @@ -5104,6 +5264,8 @@ snapshots: '@humanwhocodes/retry@0.3.1': {} + '@ioredis/commands@1.2.0': {} + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -5329,10 +5491,28 @@ snapshots: dependencies: sparse-bitfield: 3.0.3 - '@nest-modules/mailer@1.3.22(@nestjs/common@10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.5)(nodemailer@6.10.0)': + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': + optional: true + + '@nest-modules/mailer@1.3.22(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.5)(nodemailer@6.10.0)': dependencies: - '@nestjs/common': 10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1) - '@nestjs/core': 10.4.5(@nestjs/common@10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.5)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.5(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.5)(reflect-metadata@0.2.2)(rxjs@7.8.1) glob: 7.1.6 inline-css: 2.6.3 nodemailer: 6.10.0 @@ -5342,6 +5522,20 @@ snapshots: transitivePeerDependencies: - supports-color + '@nestjs/bull-shared@11.0.2(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.5)': + dependencies: + '@nestjs/common': 10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.5(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.5)(reflect-metadata@0.2.2)(rxjs@7.8.1) + tslib: 2.8.1 + + '@nestjs/bull@11.0.2(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.5)(bull@4.16.5)': + dependencies: + '@nestjs/bull-shared': 11.0.2(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.5) + '@nestjs/common': 10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.5(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.5)(reflect-metadata@0.2.2)(rxjs@7.8.1) + bull: 4.16.5 + tslib: 2.8.1 + '@nestjs/cli@10.4.5': dependencies: '@angular-devkit/core': 17.3.8(chokidar@3.6.0) @@ -5368,17 +5562,27 @@ snapshots: - uglify-js - webpack-cli - '@nestjs/common@10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1)': + '@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)': dependencies: iterare: 1.2.1 reflect-metadata: 0.2.2 rxjs: 7.8.1 tslib: 2.7.0 uid: 2.0.2 + optionalDependencies: + class-validator: 0.14.1 - '@nestjs/core@10.4.5(@nestjs/common@10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.5)(reflect-metadata@0.2.2)(rxjs@7.8.1)': + '@nestjs/config@4.0.1(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(rxjs@7.8.1)': dependencies: - '@nestjs/common': 10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + dotenv: 16.4.7 + dotenv-expand: 12.0.1 + lodash: 4.17.21 + rxjs: 7.8.1 + + '@nestjs/core@10.4.5(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.5)(reflect-metadata@0.2.2)(rxjs@7.8.1)': + dependencies: + '@nestjs/common': 10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nuxtjs/opencollective': 0.3.2 fast-safe-stringify: 2.1.1 iterare: 1.2.1 @@ -5388,37 +5592,46 @@ snapshots: tslib: 2.7.0 uid: 2.0.2 optionalDependencies: - '@nestjs/platform-express': 10.4.5(@nestjs/common@10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.5) + '@nestjs/platform-express': 10.4.5(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.5) transitivePeerDependencies: - encoding - '@nestjs/jwt@10.2.0(@nestjs/common@10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1))': + '@nestjs/jwt@10.2.0(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))': dependencies: - '@nestjs/common': 10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@types/jsonwebtoken': 9.0.5 jsonwebtoken: 9.0.2 - '@nestjs/mapped-types@2.0.5(@nestjs/common@10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)': + '@nestjs/mapped-types@2.0.5(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-validator@0.14.1)(reflect-metadata@0.2.2)': + dependencies: + '@nestjs/common': 10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + reflect-metadata: 0.2.2 + optionalDependencies: + class-validator: 0.14.1 + + '@nestjs/mapped-types@2.1.0(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-validator@0.14.1)(reflect-metadata@0.2.2)': dependencies: - '@nestjs/common': 10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) reflect-metadata: 0.2.2 + optionalDependencies: + class-validator: 0.14.1 - '@nestjs/mongoose@10.0.10(@nestjs/common@10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.5)(mongoose@8.12.1(@aws-sdk/credential-providers@3.675.0(@aws-sdk/client-sso-oidc@3.675.0(@aws-sdk/client-sts@3.675.0)))(socks@2.8.3))(rxjs@7.8.1)': + '@nestjs/mongoose@10.0.10(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.5)(mongoose@8.12.1(@aws-sdk/credential-providers@3.675.0(@aws-sdk/client-sso-oidc@3.675.0(@aws-sdk/client-sts@3.675.0)))(socks@2.8.3))(rxjs@7.8.1)': dependencies: - '@nestjs/common': 10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1) - '@nestjs/core': 10.4.5(@nestjs/common@10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.5)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.5(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.5)(reflect-metadata@0.2.2)(rxjs@7.8.1) mongoose: 8.12.1(@aws-sdk/credential-providers@3.675.0(@aws-sdk/client-sso-oidc@3.675.0(@aws-sdk/client-sts@3.675.0)))(socks@2.8.3) rxjs: 7.8.1 - '@nestjs/passport@10.0.3(@nestjs/common@10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1))(passport@0.7.0)': + '@nestjs/passport@10.0.3(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(passport@0.7.0)': dependencies: - '@nestjs/common': 10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) passport: 0.7.0 - '@nestjs/platform-express@10.4.5(@nestjs/common@10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.5)': + '@nestjs/platform-express@10.4.5(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.5)': dependencies: - '@nestjs/common': 10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1) - '@nestjs/core': 10.4.5(@nestjs/common@10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.5)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.5(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.5)(reflect-metadata@0.2.2)(rxjs@7.8.1) body-parser: 1.20.3 cors: 2.8.5 express: 4.21.1 @@ -5427,10 +5640,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@nestjs/schedule@4.1.1(@nestjs/common@10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.5)': + '@nestjs/schedule@4.1.1(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.5)': dependencies: - '@nestjs/common': 10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1) - '@nestjs/core': 10.4.5(@nestjs/common@10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.5)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.5(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.5)(reflect-metadata@0.2.2)(rxjs@7.8.1) cron: 3.1.7 uuid: 10.0.0 @@ -5456,30 +5669,32 @@ snapshots: transitivePeerDependencies: - chokidar - '@nestjs/swagger@7.4.2(@nestjs/common@10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.5)(reflect-metadata@0.2.2)': + '@nestjs/swagger@7.4.2(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.5)(class-validator@0.14.1)(reflect-metadata@0.2.2)': dependencies: '@microsoft/tsdoc': 0.15.0 - '@nestjs/common': 10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1) - '@nestjs/core': 10.4.5(@nestjs/common@10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.5)(reflect-metadata@0.2.2)(rxjs@7.8.1) - '@nestjs/mapped-types': 2.0.5(@nestjs/common@10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2) + '@nestjs/common': 10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.5(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.5)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/mapped-types': 2.0.5(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-validator@0.14.1)(reflect-metadata@0.2.2) js-yaml: 4.1.0 lodash: 4.17.21 path-to-regexp: 3.3.0 reflect-metadata: 0.2.2 swagger-ui-dist: 5.17.14 + optionalDependencies: + class-validator: 0.14.1 - '@nestjs/testing@10.4.5(@nestjs/common@10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.5)(@nestjs/platform-express@10.4.5)': + '@nestjs/testing@10.4.5(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.5)(@nestjs/platform-express@10.4.5)': dependencies: - '@nestjs/common': 10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1) - '@nestjs/core': 10.4.5(@nestjs/common@10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.5)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.5(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.5)(reflect-metadata@0.2.2)(rxjs@7.8.1) tslib: 2.7.0 optionalDependencies: - '@nestjs/platform-express': 10.4.5(@nestjs/common@10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.5) + '@nestjs/platform-express': 10.4.5(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.5) - '@nestjs/throttler@6.2.1(@nestjs/common@10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.5)(reflect-metadata@0.2.2)': + '@nestjs/throttler@6.2.1(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.5)(reflect-metadata@0.2.2)': dependencies: - '@nestjs/common': 10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1) - '@nestjs/core': 10.4.5(@nestjs/common@10.4.5(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.5)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.5(@nestjs/common@10.4.5(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.5)(reflect-metadata@0.2.2)(rxjs@7.8.1) reflect-metadata: 0.2.2 '@nodelib/fs.scandir@2.1.5': @@ -5561,7 +5776,7 @@ snapshots: '@smithy/abort-controller@3.1.5': dependencies: '@smithy/types': 3.5.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/config-resolver@3.0.9': @@ -5570,7 +5785,7 @@ snapshots: '@smithy/types': 3.5.0 '@smithy/util-config-provider': 3.0.0 '@smithy/util-middleware': 3.0.7 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/core@2.4.8': @@ -5584,7 +5799,7 @@ snapshots: '@smithy/util-body-length-browser': 3.0.0 '@smithy/util-middleware': 3.0.7 '@smithy/util-utf8': 3.0.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/credential-provider-imds@3.2.4': @@ -5593,7 +5808,7 @@ snapshots: '@smithy/property-provider': 3.1.7 '@smithy/types': 3.5.0 '@smithy/url-parser': 3.0.7 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/fetch-http-handler@3.2.9': @@ -5602,7 +5817,7 @@ snapshots: '@smithy/querystring-builder': 3.0.7 '@smithy/types': 3.5.0 '@smithy/util-base64': 3.0.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/hash-node@3.0.7': @@ -5610,30 +5825,30 @@ snapshots: '@smithy/types': 3.5.0 '@smithy/util-buffer-from': 3.0.0 '@smithy/util-utf8': 3.0.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/invalid-dependency@3.0.7': dependencies: '@smithy/types': 3.5.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/is-array-buffer@2.2.0': dependencies: - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/is-array-buffer@3.0.0': dependencies: - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/middleware-content-length@3.0.9': dependencies: '@smithy/protocol-http': 4.1.4 '@smithy/types': 3.5.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/middleware-endpoint@3.1.4': @@ -5644,7 +5859,7 @@ snapshots: '@smithy/types': 3.5.0 '@smithy/url-parser': 3.0.7 '@smithy/util-middleware': 3.0.7 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/middleware-retry@3.0.23': @@ -5656,20 +5871,20 @@ snapshots: '@smithy/types': 3.5.0 '@smithy/util-middleware': 3.0.7 '@smithy/util-retry': 3.0.7 - tslib: 2.8.0 + tslib: 2.8.1 uuid: 9.0.1 optional: true '@smithy/middleware-serde@3.0.7': dependencies: '@smithy/types': 3.5.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/middleware-stack@3.0.7': dependencies: '@smithy/types': 3.5.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/node-config-provider@3.1.8': @@ -5677,7 +5892,7 @@ snapshots: '@smithy/property-provider': 3.1.7 '@smithy/shared-ini-file-loader': 3.1.8 '@smithy/types': 3.5.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/node-http-handler@3.2.4': @@ -5686,32 +5901,32 @@ snapshots: '@smithy/protocol-http': 4.1.4 '@smithy/querystring-builder': 3.0.7 '@smithy/types': 3.5.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/property-provider@3.1.7': dependencies: '@smithy/types': 3.5.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/protocol-http@4.1.4': dependencies: '@smithy/types': 3.5.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/querystring-builder@3.0.7': dependencies: '@smithy/types': 3.5.0 '@smithy/util-uri-escape': 3.0.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/querystring-parser@3.0.7': dependencies: '@smithy/types': 3.5.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/service-error-classification@3.0.7': @@ -5722,7 +5937,7 @@ snapshots: '@smithy/shared-ini-file-loader@3.1.8': dependencies: '@smithy/types': 3.5.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/signature-v4@4.2.0': @@ -5734,7 +5949,7 @@ snapshots: '@smithy/util-middleware': 3.0.7 '@smithy/util-uri-escape': 3.0.0 '@smithy/util-utf8': 3.0.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/smithy-client@3.4.0': @@ -5744,53 +5959,53 @@ snapshots: '@smithy/protocol-http': 4.1.4 '@smithy/types': 3.5.0 '@smithy/util-stream': 3.1.9 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/types@3.5.0': dependencies: - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/url-parser@3.0.7': dependencies: '@smithy/querystring-parser': 3.0.7 '@smithy/types': 3.5.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/util-base64@3.0.0': dependencies: '@smithy/util-buffer-from': 3.0.0 '@smithy/util-utf8': 3.0.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/util-body-length-browser@3.0.0': dependencies: - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/util-body-length-node@3.0.0': dependencies: - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/util-buffer-from@2.2.0': dependencies: '@smithy/is-array-buffer': 2.2.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/util-buffer-from@3.0.0': dependencies: '@smithy/is-array-buffer': 3.0.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/util-config-provider@3.0.0': dependencies: - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/util-defaults-mode-browser@3.0.23': @@ -5799,7 +6014,7 @@ snapshots: '@smithy/smithy-client': 3.4.0 '@smithy/types': 3.5.0 bowser: 2.11.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/util-defaults-mode-node@3.0.23': @@ -5810,32 +6025,32 @@ snapshots: '@smithy/property-provider': 3.1.7 '@smithy/smithy-client': 3.4.0 '@smithy/types': 3.5.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/util-endpoints@2.1.3': dependencies: '@smithy/node-config-provider': 3.1.8 '@smithy/types': 3.5.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/util-hex-encoding@3.0.0': dependencies: - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/util-middleware@3.0.7': dependencies: '@smithy/types': 3.5.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/util-retry@3.0.7': dependencies: '@smithy/service-error-classification': 3.0.7 '@smithy/types': 3.5.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/util-stream@3.1.9': @@ -5847,24 +6062,24 @@ snapshots: '@smithy/util-buffer-from': 3.0.0 '@smithy/util-hex-encoding': 3.0.0 '@smithy/util-utf8': 3.0.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/util-uri-escape@3.0.0': dependencies: - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/util-utf8@2.3.0': dependencies: '@smithy/util-buffer-from': 2.2.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@smithy/util-utf8@3.0.0': dependencies: '@smithy/util-buffer-from': 3.0.0 - tslib: 2.8.0 + tslib: 2.8.1 optional: true '@stablelib/base64@1.0.1': {} @@ -6063,6 +6278,8 @@ snapshots: '@types/tough-cookie@4.0.5': optional: true + '@types/validator@13.12.2': {} + '@types/webidl-conversions@7.0.3': {} '@types/whatwg-url@11.0.5': @@ -6532,6 +6749,18 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 + bull@4.16.5: + dependencies: + cron-parser: 4.9.0 + get-port: 5.1.1 + ioredis: 5.6.0 + lodash: 4.17.21 + msgpackr: 1.11.2 + semver: 7.6.3 + uuid: 8.3.2 + transitivePeerDependencies: + - supports-color + busboy@1.6.0: dependencies: streamsearch: 1.1.0 @@ -6622,6 +6851,12 @@ snapshots: cjs-module-lexer@1.4.1: {} + class-validator@0.14.1: + dependencies: + '@types/validator': 13.12.2 + libphonenumber-js: 1.12.6 + validator: 13.12.0 + clean-css@4.2.4: dependencies: source-map: 0.6.1 @@ -6658,6 +6893,8 @@ snapshots: clone@1.0.4: {} + cluster-key-slot@1.1.2: {} + co@4.6.0: {} collect-v8-coverage@1.0.2: {} @@ -6761,6 +6998,10 @@ snapshots: create-require@1.1.1: {} + cron-parser@4.9.0: + dependencies: + luxon: 3.4.4 + cron@3.1.7: dependencies: '@types/luxon': 3.4.2 @@ -6828,10 +7069,15 @@ snapshots: delayed-stream@1.0.0: {} + denque@2.1.0: {} + depd@2.0.0: {} destroy@1.2.0: {} + detect-libc@2.0.3: + optional: true + detect-newline@3.1.0: {} dezalgo@1.0.4: @@ -6867,8 +7113,14 @@ snapshots: dom-serializer: 0.1.1 domelementtype: 1.3.1 + dotenv-expand@12.0.1: + dependencies: + dotenv: 16.4.7 + dotenv@16.4.5: {} + dotenv@16.4.7: {} + duplexify@4.1.3: dependencies: end-of-stream: 1.4.4 @@ -7384,6 +7636,8 @@ snapshots: get-package-type@0.1.0: {} + get-port@5.1.1: {} + get-stream@6.0.1: {} get-uri@3.0.2: @@ -7669,6 +7923,20 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 6.2.0 + ioredis@5.6.0: + dependencies: + '@ioredis/commands': 1.2.0 + cluster-key-slot: 1.1.2 + debug: 4.3.7 + 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 + transitivePeerDependencies: + - supports-color + ip-address@9.0.5: dependencies: jsbn: 1.1.0 @@ -8238,6 +8506,8 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + libphonenumber-js@1.12.6: {} + limiter@1.1.5: {} lines-and-columns@1.2.4: {} @@ -8276,6 +8546,8 @@ snapshots: lodash.includes@4.3.0: {} + lodash.isarguments@3.1.0: {} + lodash.isboolean@3.0.3: {} lodash.isinteger@4.0.4: {} @@ -8457,6 +8729,22 @@ snapshots: ms@2.1.3: {} + msgpackr-extract@3.0.3: + dependencies: + node-gyp-build-optional-packages: 5.2.2 + 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 + optional: true + + msgpackr@1.11.2: + optionalDependencies: + msgpackr-extract: 3.0.3 + multer@1.4.4-lts.1: dependencies: append-field: 1.0.0 @@ -8491,6 +8779,11 @@ snapshots: node-forge@1.3.1: {} + node-gyp-build-optional-packages@5.2.2: + dependencies: + detect-libc: 2.0.3 + optional: true + node-int64@0.4.0: {} node-releases@2.0.18: {} @@ -8872,6 +9165,12 @@ snapshots: dependencies: picomatch: 2.3.1 + redis-errors@1.2.0: {} + + redis-parser@3.0.0: + dependencies: + redis-errors: 1.2.0 + reflect-metadata@0.2.2: {} regenerator-runtime@0.11.1: @@ -9078,6 +9377,8 @@ snapshots: dependencies: escape-string-regexp: 2.0.0 + standard-as-callback@2.1.0: {} + standardwebhooks@1.0.0: dependencies: '@stablelib/base64': 1.0.1 @@ -9358,6 +9659,8 @@ snapshots: tslib@2.8.0: {} + tslib@2.8.1: {} + type-check@0.3.2: dependencies: prelude-ls: 1.1.2 @@ -9423,8 +9726,7 @@ snapshots: uuid@10.0.0: {} - uuid@8.3.2: - optional: true + uuid@8.3.2: {} uuid@9.0.1: optional: true @@ -9437,6 +9739,8 @@ snapshots: '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 + validator@13.12.0: {} + vary@1.1.2: {} void-elements@2.0.1: diff --git a/api/src/app.module.ts b/api/src/app.module.ts index 9c37f09..9d9bd55 100644 --- a/api/src/app.module.ts +++ b/api/src/app.module.ts @@ -1,4 +1,9 @@ -import { Module } from '@nestjs/common' +import { + MiddlewareConsumer, + Module, + NestModule, + RequestMethod, +} from '@nestjs/common' import { MongooseModule } from '@nestjs/mongoose' import { GatewayModule } from './gateway/gateway.module' import { AuthModule } from './auth/auth.module' @@ -7,12 +12,29 @@ import { ThrottlerModule } from '@nestjs/throttler' import { APP_GUARD } from '@nestjs/core/constants' import { WebhookModule } from './webhook/webhook.module' import { ThrottlerByIpGuard } from './auth/guards/throttle-by-ip.guard' +import { Injectable, NestMiddleware } from '@nestjs/common' +import { Request, Response, NextFunction } from 'express' import { ScheduleModule } from '@nestjs/schedule' -import { BillingModule } from './billing/billing.module'; +import { BillingModule } from './billing/billing.module' +import { ConfigModule, ConfigService } from '@nestjs/config' +import { BullModule } from '@nestjs/bull' + +@Injectable() +export class LoggerMiddleware implements NestMiddleware { + use(req: Request, res: Response, next: NextFunction) { + console.log('req.originalUrl: ', req.originalUrl) + if (next) { + next() + } + } +} @Module({ imports: [ MongooseModule.forRoot(process.env.MONGO_URI), + ConfigModule.forRoot({ + isGlobal: true, + }), ThrottlerModule.forRoot([ { ttl: 60000, @@ -20,6 +42,15 @@ import { BillingModule } from './billing/billing.module'; }, ]), ScheduleModule.forRoot(), + BullModule.forRootAsync({ + imports: [ConfigModule], + inject: [ConfigService], + useFactory: async (configService: ConfigService) => { + return { + redis: configService.get('REDIS_URL'), + } + }, + }), AuthModule, UsersModule, GatewayModule, @@ -34,4 +65,10 @@ import { BillingModule } from './billing/billing.module'; }, ], }) -export class AppModule {} +export class AppModule implements NestModule { + configure(consumer: MiddlewareConsumer) { + consumer + .apply(LoggerMiddleware) + .forRoutes({ path: '*', method: RequestMethod.ALL }) + } +} diff --git a/api/src/gateway/gateway.module.ts b/api/src/gateway/gateway.module.ts index 8eb3d41..f5cc607 100644 --- a/api/src/gateway/gateway.module.ts +++ b/api/src/gateway/gateway.module.ts @@ -9,6 +9,10 @@ import { SMS, SMSSchema } from './schemas/sms.schema' import { SMSBatch, SMSBatchSchema } from './schemas/sms-batch.schema' import { WebhookModule } from 'src/webhook/webhook.module' import { BillingModule } from 'src/billing/billing.module' +import { BullModule } from '@nestjs/bull' +import { ConfigModule } from '@nestjs/config' +import { SmsQueueService } from './queue/sms-queue.service' +import { SmsQueueProcessor } from './queue/sms-queue.processor' @Module({ imports: [ @@ -26,13 +30,26 @@ import { BillingModule } from 'src/billing/billing.module' schema: SMSBatchSchema, }, ]), + BullModule.registerQueue({ + name: 'sms', + defaultJobOptions: { + attempts: 2, + backoff: { + type: 'exponential', + delay: 1000, + }, + removeOnComplete: false, + removeOnFail: false, + }, + }), AuthModule, UsersModule, WebhookModule, forwardRef(() => BillingModule), + ConfigModule, ], controllers: [GatewayController], - providers: [GatewayService], - exports: [MongooseModule, GatewayService], + providers: [GatewayService, SmsQueueService, SmsQueueProcessor], + exports: [MongooseModule, GatewayService, SmsQueueService], }) export class GatewayModule {} diff --git a/api/src/gateway/gateway.service.ts b/api/src/gateway/gateway.service.ts index 100d95a..00e427f 100644 --- a/api/src/gateway/gateway.service.ts +++ b/api/src/gateway/gateway.service.ts @@ -15,13 +15,12 @@ import { AuthService } from 'src/auth/auth.service' import { SMS } from './schemas/sms.schema' import { SMSType } from './sms-type.enum' import { SMSBatch } from './schemas/sms-batch.schema' -import { - BatchResponse, - Message, -} from 'firebase-admin/messaging' +import { BatchResponse, Message } from 'firebase-admin/messaging' import { WebhookEvent } from 'src/webhook/webhook-event.enum' import { WebhookService } from 'src/webhook/webhook.service' import { BillingService } from 'src/billing/billing.service' +import { SmsQueueService } from './queue/sms-queue.service' + @Injectable() export class GatewayService { constructor( @@ -31,6 +30,7 @@ export class GatewayService { private authService: AuthService, private webhookService: WebhookService, private billingService: BillingService, + private smsQueueService: SmsQueueService, ) {} async registerDevice( @@ -151,6 +151,7 @@ export class GatewayService { message, recipientCount: recipients.length, recipientPreview: this.getRecipientsPreview(recipients), + status: 'pending', }) } catch (e) { throw new HttpException( @@ -173,6 +174,7 @@ export class GatewayService { type: SMSType.SENT, recipient, requestedAt: new Date(), + status: 'pending', }) const updatedSMSData = { smsId: sms._id, @@ -198,6 +200,50 @@ export class GatewayService { fcmMessages.push(fcmMessage) } + // Check if we should use the queue + if (this.smsQueueService.isQueueEnabled()) { + try { + // Update batch status to processing + await this.smsBatchModel.findByIdAndUpdate(smsBatch._id, { + $set: { status: 'processing' }, + }) + + // Add to queue + await this.smsQueueService.addSendSmsJob( + deviceId, + fcmMessages, + smsBatch._id.toString(), + ) + + return { + success: true, + message: 'SMS added to queue for processing', + smsBatchId: smsBatch._id, + recipientCount: recipients.length, + } + } catch (e) { + // Update batch status to failed + await this.smsBatchModel.findByIdAndUpdate(smsBatch._id, { + $set: { status: 'failed', error: e.message }, + }) + + // Update all SMS in batch to failed + await this.smsModel.updateMany( + { smsBatch: smsBatch._id }, + { $set: { status: 'failed', error: e.message } }, + ) + + throw new HttpException( + { + success: false, + error: 'Failed to add SMS to queue', + additionalInfo: e, + }, + HttpStatus.INTERNAL_SERVER_ERROR, + ) + } + } + try { const response = await firebaseAdmin.messaging().sendEach(fcmMessages) @@ -223,8 +269,26 @@ export class GatewayService { console.log('Failed to update sentSMSCount') console.log(e) }) + + this.smsBatchModel + .findByIdAndUpdate(smsBatch._id, { + $set: { status: 'completed' }, + }) + .exec() + .catch((e) => { + console.error('failed to update sms batch status to completed') + }) + return response } catch (e) { + this.smsBatchModel + .findByIdAndUpdate(smsBatch._id, { + $set: { status: 'failed', error: e.message }, + }) + .exec() + .catch((e) => { + console.error('failed to update sms batch status to failed') + }) throw new HttpException( { success: false, @@ -249,7 +313,6 @@ export class GatewayService { ) } - if ( !Array.isArray(body.messages) || body.messages.length === 0 || @@ -281,9 +344,11 @@ export class GatewayService { recipientPreview: this.getRecipientsPreview( messages.map((m) => m.recipients).flat(), ), + status: 'pending', }) - const fcmResponses: BatchResponse[] = [] + const fcmMessages: Message[] = [] + for (const smsData of messages) { const message = smsData.message const recipients = smsData.recipients @@ -296,8 +361,6 @@ export class GatewayService { continue } - const fcmMessages: Message[] = [] - for (const recipient of recipients) { const sms = await this.smsModel.create({ device: device._id, @@ -306,6 +369,7 @@ export class GatewayService { type: SMSType.SENT, recipient, requestedAt: new Date(), + status: 'pending', }) const updatedSMSData = { smsId: sms._id, @@ -330,9 +394,58 @@ export class GatewayService { } fcmMessages.push(fcmMessage) } + } + + // Check if we should use the queue + if (this.smsQueueService.isQueueEnabled()) { + try { + // Add to queue + await this.smsQueueService.addSendSmsJob( + deviceId, + fcmMessages, + smsBatch._id.toString(), + ) + + return { + success: true, + message: 'Bulk SMS added to queue for processing', + smsBatchId: smsBatch._id, + recipientCount: messages.map((m) => m.recipients).flat().length, + } + } catch (e) { + // Update batch status to failed + await this.smsBatchModel.findByIdAndUpdate(smsBatch._id, { + $set: { + status: 'failed', + error: e.message, + successCount: 0, + failureCount: fcmMessages.length, + }, + }) + + // Update all SMS in batch to failed + await this.smsModel.updateMany( + { smsBatch: smsBatch._id }, + { $set: { status: 'failed', error: e.message } }, + ) + + throw new HttpException( + { + success: false, + error: 'Failed to add bulk SMS to queue', + additionalInfo: e, + }, + HttpStatus.INTERNAL_SERVER_ERROR, + ) + } + } + + const fcmMessagesBatches = fcmMessages.map((m) => [m]) + const fcmResponses: BatchResponse[] = [] + for (const batch of fcmMessagesBatches) { try { - const response = await firebaseAdmin.messaging().sendEach(fcmMessages) + const response = await firebaseAdmin.messaging().sendEach(batch) console.log(response) fcmResponses.push(response) @@ -346,9 +459,27 @@ export class GatewayService { console.log('Failed to update sentSMSCount') console.log(e) }) + + this.smsBatchModel + .findByIdAndUpdate(smsBatch._id, { + $set: { status: 'completed' }, + }) + .exec() + .catch((e) => { + console.error('failed to update sms batch status to completed') + }) } catch (e) { console.log('Failed to send SMS: FCM') console.log(e) + + this.smsBatchModel + .findByIdAndUpdate(smsBatch._id, { + $set: { status: 'failed', error: e.message }, + }) + .exec() + .catch((e) => { + console.error('failed to update sms batch status to failed') + }) } } @@ -438,7 +569,11 @@ export class GatewayService { return sms } - async getReceivedSMS(deviceId: string, page = 1, limit = 50): Promise<{ data: any[], meta: any }> { + async getReceivedSMS( + deviceId: string, + page = 1, + limit = 50, + ): Promise<{ data: any[]; meta: any }> { const device = await this.deviceModel.findById(deviceId) if (!device) { @@ -452,13 +587,13 @@ export class GatewayService { } // Calculate skip value for pagination - const skip = (page - 1) * limit; + const skip = (page - 1) * limit // Get total count for pagination metadata const total = await this.smsModel.countDocuments({ device: device._id, type: SMSType.RECEIVED, - }); + }) // @ts-ignore const data = await this.smsModel @@ -468,10 +603,10 @@ export class GatewayService { type: SMSType.RECEIVED, }, null, - { - sort: { receivedAt: -1 }, + { + sort: { receivedAt: -1 }, limit: limit, - skip: skip + skip: skip, }, ) .populate({ @@ -481,8 +616,8 @@ export class GatewayService { .lean() // Use lean() to return plain JavaScript objects instead of Mongoose documents // Calculate pagination metadata - const totalPages = Math.ceil(total / limit); - + const totalPages = Math.ceil(total / limit) + return { meta: { page, @@ -491,10 +626,15 @@ export class GatewayService { totalPages, }, data, - }; + } } - async getMessages(deviceId: string, type = '', page = 1, limit = 50): Promise<{ data: any[], meta: any }> { + async getMessages( + deviceId: string, + type = '', + page = 1, + limit = 50, + ): Promise<{ data: any[]; meta: any }> { const device = await this.deviceModel.findById(deviceId) if (!device) { @@ -508,32 +648,28 @@ export class GatewayService { } // Calculate skip value for pagination - const skip = (page - 1) * limit; + const skip = (page - 1) * limit // Build query based on type filter - const query: any = { device: device._id }; - + const query: any = { device: device._id } + if (type === 'sent') { - query.type = SMSType.SENT; + query.type = SMSType.SENT } else if (type === 'received') { - query.type = SMSType.RECEIVED; + query.type = SMSType.RECEIVED } // Get total count for pagination metadata - const total = await this.smsModel.countDocuments(query); + const total = await this.smsModel.countDocuments(query) // @ts-ignore const data = await this.smsModel - .find( - query, - null, - { - // Sort by the most recent timestamp (receivedAt for received, sentAt for sent) - sort: { createdAt: -1 }, - limit: limit, - skip: skip - }, - ) + .find(query, null, { + // Sort by the most recent timestamp (receivedAt for received, sentAt for sent) + sort: { createdAt: -1 }, + limit: limit, + skip: skip, + }) .populate({ path: 'device', select: '_id brand model buildId enabled', @@ -541,8 +677,8 @@ export class GatewayService { .lean() // Use lean() to return plain JavaScript objects instead of Mongoose documents // Calculate pagination metadata - const totalPages = Math.ceil(total / limit); - + const totalPages = Math.ceil(total / limit) + return { meta: { page, @@ -551,7 +687,7 @@ export class GatewayService { totalPages, }, data, - }; + } } async getStatsForUser(user: User) { diff --git a/api/src/gateway/queue/sms-queue.processor.ts b/api/src/gateway/queue/sms-queue.processor.ts new file mode 100644 index 0000000..528dc54 --- /dev/null +++ b/api/src/gateway/queue/sms-queue.processor.ts @@ -0,0 +1,102 @@ +import { Process, Processor } from '@nestjs/bull' +import { InjectModel } from '@nestjs/mongoose' +import { Job } from 'bull' +import { Model } from 'mongoose' +import * as firebaseAdmin from 'firebase-admin' +import { Device } from '../schemas/device.schema' +import { SMS } from '../schemas/sms.schema' +import { SMSBatch } from '../schemas/sms-batch.schema' +import { WebhookService } from 'src/webhook/webhook.service' +import { Logger } from '@nestjs/common' + +@Processor('sms') +export class SmsQueueProcessor { + private readonly logger = new Logger(SmsQueueProcessor.name) + + constructor( + @InjectModel(Device.name) private deviceModel: Model, + @InjectModel(SMS.name) private smsModel: Model, + @InjectModel(SMSBatch.name) private smsBatchModel: Model, + private webhookService: WebhookService, + ) {} + + @Process({ + name: 'send-sms', + concurrency: 10, + }) + async handleSendSms(job: Job) { + this.logger.debug(`Processing send-sms job ${job.id}`) + const { deviceId, fcmMessages, smsBatchId } = job.data + + try { + this.smsBatchModel + .findByIdAndUpdate(smsBatchId, { + $set: { status: 'processing' }, + }) + .exec() + .catch((error) => { + this.logger.error( + `Failed to update sms batch status to processing ${smsBatchId}`, + error, + ) + throw error + }) + + const response = await firebaseAdmin.messaging().sendEach(fcmMessages) + + this.logger.debug( + `SMS Job ${job.id} completed, success: ${response.successCount}, failures: ${response.failureCount}`, + ) + + // Update device SMS count + await this.deviceModel + .findByIdAndUpdate(deviceId, { + $inc: { sentSMSCount: response.successCount }, + }) + .exec() + + // Update batch status + const smsBatch = await this.smsBatchModel.findByIdAndUpdate( + smsBatchId, + { + $inc: { + successCount: response.successCount, + failureCount: response.failureCount, + }, + }, + { returnDocument: 'after' }, + ) + + if (smsBatch.successCount === smsBatch.recipientCount) { + await this.smsBatchModel.findByIdAndUpdate(smsBatchId, { + $set: { status: 'completed' }, + }) + } + + return response + } catch (error) { + this.logger.error(`Failed to process SMS job ${job.id}`, error) + + const smsBatch = await this.smsBatchModel.findByIdAndUpdate( + smsBatchId, + { + $inc: { + failureCount: fcmMessages.length, + }, + }, + { returnDocument: 'after' }, + ) + + const newStatus = + smsBatch.failureCount === smsBatch.recipientCount + ? 'failed' + : 'partial_success' + + await this.smsBatchModel.findByIdAndUpdate(smsBatchId, { + $set: { status: newStatus }, + }) + + throw error + } + } +} diff --git a/api/src/gateway/queue/sms-queue.service.ts b/api/src/gateway/queue/sms-queue.service.ts new file mode 100644 index 0000000..a2670af --- /dev/null +++ b/api/src/gateway/queue/sms-queue.service.ts @@ -0,0 +1,66 @@ +import { Injectable, Logger } from '@nestjs/common' +import { InjectQueue } from '@nestjs/bull' +import { Queue } from 'bull' +import { ConfigService } from '@nestjs/config' +import { Message } from 'firebase-admin/messaging' + +@Injectable() +export class SmsQueueService { + private readonly logger = new Logger(SmsQueueService.name) + private readonly useSmsQueue: boolean + private readonly maxSmsBatchSize: number + + constructor( + @InjectQueue('sms') private readonly smsQueue: Queue, + private readonly configService: ConfigService, + ) { + this.useSmsQueue = this.configService.get('USE_SMS_QUEUE', false) + this.maxSmsBatchSize = this.configService.get( + 'MAX_SMS_BATCH_SIZE', + 5, + ) + } + + /** + * Check if queue is enabled based on environment variable + */ + isQueueEnabled(): boolean { + return this.useSmsQueue + } + + async addSendSmsJob( + deviceId: string, + fcmMessages: Message[], + smsBatchId: string, + ) { + this.logger.debug(`Adding send-sms job for batch ${smsBatchId}`) + + // Split messages into batches of max smsBatchSize messages + const batches = [] + for (let i = 0; i < fcmMessages.length; i += this.maxSmsBatchSize) { + batches.push(fcmMessages.slice(i, i + this.maxSmsBatchSize)) + } + + for (const batch of batches) { + await this.smsQueue.add( + 'send-sms', + { + deviceId, + fcmMessages: batch, + smsBatchId, + }, + { + priority: 1, // TODO: Make this dynamic based on users subscription plan + attempts: 1, + delay: 1000, // 1 second + backoff: { + type: 'exponential', + delay: 5000, // 5 seconds + }, + removeOnComplete: false, + removeOnFail: false, + }, + ) + } + } +} diff --git a/api/src/gateway/schemas/sms-batch.schema.ts b/api/src/gateway/schemas/sms-batch.schema.ts index bc48e89..3fee3e6 100644 --- a/api/src/gateway/schemas/sms-batch.schema.ts +++ b/api/src/gateway/schemas/sms-batch.schema.ts @@ -26,6 +26,21 @@ export class SMSBatch { @Prop({ type: String }) recipientPreview: string + @Prop({ type: Number, default: 0 }) + successCount: number + + @Prop({ type: Number, default: 0 }) + failureCount: number + + @Prop({ type: String, default: 'pending', enum: ['pending', 'processing', 'completed', 'partial_success', 'failed'] }) + status: string + + @Prop({ type: String }) + error: string + + @Prop({ type: Date }) + completedAt: Date + // misc metadata for debugging @Prop({ type: Object }) metadata: Record diff --git a/api/src/gateway/schemas/sms.schema.ts b/api/src/gateway/schemas/sms.schema.ts index 286c082..401ba75 100644 --- a/api/src/gateway/schemas/sms.schema.ts +++ b/api/src/gateway/schemas/sms.schema.ts @@ -53,8 +53,11 @@ export class SMS { // @Prop({ type: String }) // failureReason: string - // @Prop({ type: String }) - // status: string + @Prop({ type: String, default: 'pending', enum: ['pending', 'sent', 'delivered', 'failed'] }) + status: string + + @Prop({ type: String }) + error: string // misc metadata for debugging @Prop({ type: Object })