diff --git a/database/connection.ts b/database/connection.ts index 2122a03..926e372 100644 --- a/database/connection.ts +++ b/database/connection.ts @@ -2,6 +2,8 @@ import { join } from "path"; import { config } from "dotenv"; import { DataSource } from "typeorm"; + +import { TicketMessageAttachment } from "../src/modules/tickets/entities/ticket-message-attachment"; config(); const { DB_HOST, DB_NAME, DB_PASS, DB_PORT, DB_USER, NODE_ENV } = process.env; @@ -16,7 +18,7 @@ export const connectionSource = new DataSource({ password: DB_PASS, database: DB_NAME, logging: true, - entities: [join(process.cwd(), PATH, "modules/**/*.entity{.ts,.js}")], + entities: [join(process.cwd(), PATH, "modules/**/*.entity{.ts,.js}"), TicketMessageAttachment], migrations: [join(process.cwd(), "database/migrations/*{.ts,.js}")], synchronize: false, migrationsTableName: "typeorm_migrations", diff --git a/database/seeder.runner.ts b/database/seeder.runner.ts index cfb92f2..a462d01 100644 --- a/database/seeder.runner.ts +++ b/database/seeder.runner.ts @@ -2,7 +2,7 @@ import { Logger } from "@nestjs/common"; import { connectionSource } from "./connection"; import { seedAdmin } from "./seeders/admin.seeder"; -import { seedNotifSettings } from "./seeders/notif-settting.seeder"; +import { seedNotifSettings } from "./seeders/notif-setting.seeder"; import { seedRole } from "./seeders/role.seeder"; const logger = new Logger("Seeder"); diff --git a/database/seeders/notif-settting.seeder.ts b/database/seeders/notif-setting.seeder.ts similarity index 94% rename from database/seeders/notif-settting.seeder.ts rename to database/seeders/notif-setting.seeder.ts index f67c893..1061d2b 100644 --- a/database/seeders/notif-settting.seeder.ts +++ b/database/seeders/notif-setting.seeder.ts @@ -13,7 +13,7 @@ const notifSettings = [ { title: NotifSettingsEnum.CREATE_INVOICE, title_fa: FarsiNotifSettingsEnum.CREATE_INVOICE }, { title: NotifSettingsEnum.CREATE_TICKET, title_fa: FarsiNotifSettingsEnum.CREATE_TICKET }, { title: NotifSettingsEnum.UNBLOCKING_SERVICE, title_fa: FarsiNotifSettingsEnum.UNBLOCKING_SERVICE }, - { title: NotifSettingsEnum.CREATE_SERIVCE, title_fa: FarsiNotifSettingsEnum.CREATE_SERIVCE }, + { title: NotifSettingsEnum.CREATE_SERVICE, title_fa: FarsiNotifSettingsEnum.CREATE_SERVICE }, ]; export const seedNotifSettings = async (dataSource: DataSource, logger: Logger) => { diff --git a/package.json b/package.json index c89d02e..37922c1 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "@nest-lab/throttler-storage-redis": "^1.0.0", "@nestjs-modules/mailer": "^2.0.2", "@nestjs/axios": "^4.0.0", + "@nestjs/bullmq": "^11.0.2", "@nestjs/cache-manager": "^3.0.0", "@nestjs/common": "^10.4.15", "@nestjs/config": "^4.0.0", @@ -50,6 +51,7 @@ "aws-sdk": "^2.1692.0", "axios": "^1.7.9", "bcrypt": "^5.1.1", + "bullmq": "^5.40.0", "cache-manager": "^6.3.2", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 99e7f15..eeb59aa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,6 +29,9 @@ importers: '@nestjs/axios': specifier: ^4.0.0 version: 4.0.0(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(axios@1.7.9)(rxjs@7.8.1) + '@nestjs/bullmq': + specifier: ^11.0.2 + version: 11.0.2(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1))(bullmq@5.40.0) '@nestjs/cache-manager': specifier: ^3.0.0 version: 3.0.0(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1))(cache-manager@6.3.2)(rxjs@7.8.1) @@ -71,6 +74,9 @@ importers: bcrypt: specifier: ^5.1.1 version: 5.1.1 + bullmq: + specifier: ^5.40.0 + version: 5.40.0 cache-manager: specifier: ^6.3.2 version: 6.3.2 @@ -889,6 +895,36 @@ packages: '@microsoft/tsdoc@0.15.1': resolution: {integrity: sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==} + '@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-lab/fastify-multer@1.3.0': resolution: {integrity: sha512-/4LocG7pBhwwbqN33YiPbv+93NdxlL/z/B2EL+hpulIr+qn2eLs/CxMg8jHCmtGCodA7NFHFERqIh75Fc2fryQ==} peerDependencies: @@ -919,6 +955,19 @@ packages: axios: ^1.3.1 rxjs: ^7.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/bullmq@11.0.2': + resolution: {integrity: sha512-Lq6lGpKkETsm0RDcUktlzsthFoE3A5QTMp2FwPi1eztKqKD6/90KS1TcnC9CJFzjpUaYnQzIMrlNs55e+/wsHA==} + 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.0.0': resolution: {integrity: sha512-csKvxHSQWfC0OiDo0bNEhLqrmYDopHEvRyC81MxV9xFj1AO+rOKocpHa4M1ZGH//6uKFIPGN9oiR0mvZY77APA==} peerDependencies: @@ -1890,6 +1939,9 @@ packages: buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + bullmq@5.40.0: + resolution: {integrity: sha512-tmrk32EmcbtUOGPSdwlDUcc0w+nAMqCisk8vEFFmG8aOzIehz0BxTNSj6Grh0qoMugRF3VglWk8HGUBnWqU2Fw==} + cache-manager@6.3.2: resolution: {integrity: sha512-VmLouPUrvpm9dfwYB6OE7YVXDZ7BCfbt7hq10EHiBYaW9K9ZthK1bbjDQAtXGDK7d9u8t4G/7dMWSJOwN33msg==} @@ -2170,6 +2222,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@6.0.6: resolution: {integrity: sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==} engines: {node: '>=4.8'} @@ -3680,6 +3736,10 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + luxon@3.5.0: + resolution: {integrity: sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==} + engines: {node: '>=12'} + magic-string@0.30.8: resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==} engines: {node: '>=12'} @@ -3917,6 +3977,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==} + mute-stream@0.0.8: resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} @@ -3957,6 +4024,10 @@ packages: encoding: optional: true + 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==} @@ -6535,6 +6606,24 @@ snapshots: '@microsoft/tsdoc@0.15.1': {} + '@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-lab/fastify-multer@1.3.0(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-fastify@10.4.15(@fastify/static@7.0.4)(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1)))(rxjs@7.8.1)': dependencies: '@nestjs/common': 10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -6577,6 +6666,20 @@ snapshots: axios: 1.7.9 rxjs: 7.8.1 + '@nestjs/bull-shared@11.0.2(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1))': + dependencies: + '@nestjs/common': 10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1) + tslib: 2.8.1 + + '@nestjs/bullmq@11.0.2(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1))(bullmq@5.40.0)': + dependencies: + '@nestjs/bull-shared': 11.0.2(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1)) + '@nestjs/common': 10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1) + bullmq: 5.40.0 + tslib: 2.8.1 + '@nestjs/cache-manager@3.0.0(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1))(cache-manager@6.3.2)(rxjs@7.8.1)': dependencies: '@nestjs/common': 10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -7838,6 +7941,18 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 + bullmq@5.40.0: + dependencies: + cron-parser: 4.9.0 + ioredis: 5.4.2 + msgpackr: 1.11.2 + node-abort-controller: 3.1.1 + semver: 7.6.3 + tslib: 2.8.1 + uuid: 9.0.1 + transitivePeerDependencies: + - supports-color + cache-manager@6.3.2: dependencies: keyv: 5.2.3 @@ -8140,6 +8255,10 @@ snapshots: create-require@1.1.1: {} + cron-parser@4.9.0: + dependencies: + luxon: 3.5.0 + cross-spawn@6.0.6: dependencies: nice-try: 1.0.5 @@ -10084,6 +10203,8 @@ snapshots: dependencies: yallist: 3.1.1 + luxon@3.5.0: {} + magic-string@0.30.8: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 @@ -10534,6 +10655,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 + mute-stream@0.0.8: {} mute-stream@1.0.0: {} @@ -10568,6 +10705,11 @@ snapshots: dependencies: whatwg-url: 5.0.0 + 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.19: {} diff --git a/src/common/constants/index.ts b/src/common/constants/index.ts index 8fb1921..38a1149 100644 --- a/src/common/constants/index.ts +++ b/src/common/constants/index.ts @@ -2,3 +2,7 @@ export const AUTH_THROTTLE_TTL = 5 * 60 * 1000; export const AUTH_THROTTLE_LIMIT = 5; export const JWT_STRATEGY_NAME = "jwt_Strategy"; + +export const PAYMENT = Object.freeze({ + PAYMENT_QUEUE: "payment", +}); diff --git a/src/common/queues/worker.processor.ts b/src/common/queues/worker.processor.ts new file mode 100644 index 0000000..3691b27 --- /dev/null +++ b/src/common/queues/worker.processor.ts @@ -0,0 +1,33 @@ +import { OnWorkerEvent, WorkerHost } from "@nestjs/bullmq"; +import { Logger } from "@nestjs/common"; +import { Job } from "bullmq"; + +export abstract class WorkerProcessor extends WorkerHost { + protected readonly logger = new Logger(WorkerProcessor.name); + + @OnWorkerEvent("completed") + onCompleted(job: Job) { + const { id, name, queueName, finishedOn, returnvalue } = job; + const completionTime = finishedOn ? new Date(finishedOn).toISOString() : ""; + this.logger.log(`Job id: ${id}, name: ${name} completed in queue ${queueName} on ${completionTime}. Result: ${returnvalue}`); + } + + @OnWorkerEvent("progress") + onProgress(job: Job) { + const { id, name, progress } = job; + this.logger.log(`Job id: ${id}, name: ${name} completes ${progress}%`); + } + + @OnWorkerEvent("failed") + onFailed(job: Job) { + const { id, name, queueName, failedReason } = job; + this.logger.error(`Job id: ${id}, name: ${name} failed in queue ${queueName}. Failed reason: ${failedReason}`); + } + + @OnWorkerEvent("active") + onActive(job: Job) { + const { id, name, queueName, timestamp } = job; + const startTime = timestamp ? new Date(timestamp).toISOString() : ""; + this.logger.log(`Job id: ${id}, name: ${name} starts in queue ${queueName} on ${startTime}.`); + } +} diff --git a/src/configs/bullmq.config.ts b/src/configs/bullmq.config.ts new file mode 100644 index 0000000..fb6ef96 --- /dev/null +++ b/src/configs/bullmq.config.ts @@ -0,0 +1,18 @@ +import { SharedBullAsyncConfiguration } from "@nestjs/bullmq"; +import { ConfigService } from "@nestjs/config"; + +export function bullMqConfig(): SharedBullAsyncConfiguration { + return { + inject: [ConfigService], + useFactory: async (configService: ConfigService) => ({ + connection: { + url: configService.getOrThrow("REDIS_URI"), + }, + defaultJobOptions: { + removeOnComplete: 1000, + removeOnFail: 5000, + attempts: 3, + }, + }), + }; +} diff --git a/src/modules/auth/providers/auth.service.ts b/src/modules/auth/providers/auth.service.ts index 645eca2..991c0f4 100644 --- a/src/modules/auth/providers/auth.service.ts +++ b/src/modules/auth/providers/auth.service.ts @@ -1,4 +1,5 @@ import { BadRequestException, Injectable, Logger } from "@nestjs/common"; +import { DataSource } from "typeorm"; import { TokensService } from "./tokens.service"; import { AuthMessage, UserMessage } from "../../../common/enums/message.enum"; @@ -20,6 +21,7 @@ export class AuthService { private readonly passwordService: PasswordService, private readonly otpService: OTPService, private readonly tokensService: TokensService, + private readonly dataSource: DataSource, ) {} //****************** */ //****************** */ @@ -48,20 +50,33 @@ export class AuthService { //****************** */ async completeRegistration(completeRegistrationDto: CompleteRegistrationDto) { const { phone, code } = completeRegistrationDto; - const isValid = await this.otpService.verifyOtp(phone, code, "REGISTER"); + const queryRunner = this.dataSource.createQueryRunner(); - if (!isValid) throw new BadRequestException(AuthMessage.INVALID_OTP); + await queryRunner.connect(); + await queryRunner.startTransaction(); + try { + const isValid = await this.otpService.verifyOtp(phone, code, "REGISTER"); - await this.otpService.delOtpFormCache(phone, "REGISTER"); - const hashedPassword = await this.passwordService.hashPassword(completeRegistrationDto.password); - const user = await this.usersService.createUser(completeRegistrationDto, hashedPassword); + if (!isValid) throw new BadRequestException(AuthMessage.INVALID_OTP); - const tokens = this.tokensService.generateAccessAndRefreshToken({ sub: user.id, role: user.role.name }); + await this.otpService.delOtpFormCache(phone, "REGISTER"); + const hashedPassword = await this.passwordService.hashPassword(completeRegistrationDto.password); + const user = await this.usersService.createUser(completeRegistrationDto, hashedPassword, queryRunner); - return { - message: AuthMessage.USER_REGISTER_SUCCESS, - ...tokens, - }; + const tokens = this.tokensService.generateAccessAndRefreshToken({ sub: user.id, role: user.role.name }); + + await queryRunner.commitTransaction(); + + return { + message: AuthMessage.USER_REGISTER_SUCCESS, + ...tokens, + }; + } catch (error) { + await queryRunner.rollbackTransaction(); + throw error; + } finally { + await queryRunner.release(); + } } //****************** */ //****************** */ diff --git a/src/modules/payments/entities/payment-method.entity.ts b/src/modules/payments/entities/payment-method.entity.ts index 6a52fc1..a3da052 100644 --- a/src/modules/payments/entities/payment-method.entity.ts +++ b/src/modules/payments/entities/payment-method.entity.ts @@ -3,17 +3,8 @@ import { Column, Entity, ManyToOne } from "typeorm"; import { BankAccount } from "./bank-account.entity"; import { BaseEntity } from "../../../common/entities/base.entity"; -export enum PaymentMethodType { - CARD_TO_CARD = "CARD_TO_CARD", - GATEWAY = "GATEWAY", - SHEBA = "SHEBA", -} - @Entity() export class PaymentMethod extends BaseEntity { - @Column({ type: "enum", enum: PaymentMethodType, default: PaymentMethodType.GATEWAY }) - type: PaymentMethodType; - @ManyToOne(() => BankAccount, { nullable: true, onDelete: "SET NULL" }) bankAccount?: BankAccount; @@ -22,7 +13,4 @@ export class PaymentMethod extends BaseEntity { @Column({ type: "text", nullable: true }) description?: string; - - @Column({ type: "boolean", default: true }) - isActive: boolean; } diff --git a/src/modules/payments/entities/payment.entity.ts b/src/modules/payments/entities/payment.entity.ts index f2f3620..6b1be48 100644 --- a/src/modules/payments/entities/payment.entity.ts +++ b/src/modules/payments/entities/payment.entity.ts @@ -4,6 +4,7 @@ import { PaymentMethod } from "./payment-method.entity"; import { BaseEntity } from "../../../common/entities/base.entity"; import { User } from "../../users/entities/user.entity"; import { PaymentStatus } from "../enums/payment-status.enum"; +import { PaymentType } from "../enums/payment-type.enum"; @Entity() export class Payment extends BaseEntity { @@ -19,6 +20,9 @@ export class Payment extends BaseEntity { @Column({ type: "enum", enum: PaymentStatus, default: PaymentStatus.PENDING }) status: PaymentStatus; + @Column({ type: "enum", enum: PaymentType, nullable: false }) + type: PaymentType; + @ManyToOne(() => User, (user) => user.payments, { nullable: false, onDelete: "RESTRICT" }) user: User; diff --git a/src/modules/payments/enums/payment-type.enum.ts b/src/modules/payments/enums/payment-type.enum.ts new file mode 100644 index 0000000..6f0230c --- /dev/null +++ b/src/modules/payments/enums/payment-type.enum.ts @@ -0,0 +1,5 @@ +export enum PaymentType { + CARD_TO_CARD = "CARD_TO_CARD", + GATEWAY = "GATEWAY", + SHEBA = "SHEBA", +} diff --git a/src/modules/payments/payments.controller.ts b/src/modules/payments/payments.controller.ts index 334e6fd..799b11d 100644 --- a/src/modules/payments/payments.controller.ts +++ b/src/modules/payments/payments.controller.ts @@ -15,4 +15,7 @@ export class PaymentsController { getAvailableGateways() { return this.paymentsService.getAvailableGateways(); } + + @Get("verify") + verifyPayment() {} } diff --git a/src/modules/payments/providers/payments.service.ts b/src/modules/payments/providers/payments.service.ts index 5a59568..6304626 100644 --- a/src/modules/payments/providers/payments.service.ts +++ b/src/modules/payments/providers/payments.service.ts @@ -1,9 +1,11 @@ import { Injectable } from "@nestjs/common"; import { QueryRunner } from "typeorm"; +import { WalletMessage } from "../../../common/enums/message.enum"; import { User } from "../../users/entities/user.entity"; -// import { PaymentMethod } from "../entities/payment-method.entity"; +import { PaymentMethod } from "../entities/payment-method.entity"; import { Payment } from "../entities/payment.entity"; +import { PaymentType } from "../enums/payment-type.enum"; import { PaymentGatewayFactory } from "../factories/payment.factory"; // import { PaymentsRepository } from "../repositories/payments.repository"; import { GatewayType } from "../types/gateway.type"; @@ -22,13 +24,20 @@ export class PaymentsService { } //*********************************** */ //*********************************** */ - async createPaymentForUser(user: User, amount: number, reference: string, queryRunner: QueryRunner) { - // const paymentMethod = queryRunner.manager.create(PaymentMethod, {}); + async createPaymentForUser(user: User, amount: number, reference: string, gateway: GatewayType, queryRunner: QueryRunner) { + const paymentMethod = queryRunner.manager.create(PaymentMethod, { + gatewayName: gateway, + description: WalletMessage.DEPOSIT_WALLET_IPG, + }); + + await queryRunner.manager.save(PaymentMethod, paymentMethod); const payment = queryRunner.manager.create(Payment, { + type: PaymentType.GATEWAY, amount, reference, user, + paymentMethod, }); await queryRunner.manager.save(Payment, payment); diff --git a/src/modules/payments/queue/payment.queue.ts b/src/modules/payments/queue/payment.queue.ts new file mode 100644 index 0000000..6d1056b --- /dev/null +++ b/src/modules/payments/queue/payment.queue.ts @@ -0,0 +1,16 @@ +import { Processor } from "@nestjs/bullmq"; +import { Logger } from "@nestjs/common"; +import { Job } from "bullmq"; + +import { PAYMENT } from "../../../common/constants"; +import { WorkerProcessor } from "../../../common/queues/worker.processor"; + +@Processor(PAYMENT.PAYMENT_QUEUE) +export class PaymentProcessor extends WorkerProcessor { + protected readonly logger = new Logger(PaymentProcessor.name); + + process(job: Job, token?: string) { + this.logger.log(job, token); + return Promise.resolve("test"); + } +} diff --git a/src/modules/settings/enums/notif-settings.enum.ts b/src/modules/settings/enums/notif-settings.enum.ts index ee1a23d..faf49c4 100644 --- a/src/modules/settings/enums/notif-settings.enum.ts +++ b/src/modules/settings/enums/notif-settings.enum.ts @@ -1,7 +1,7 @@ export enum NotifSettingsEnum { ANNOUNCE_NOTIF = "ANNOUNCE_NOTIF", BILL_PAYMENT = "BILL_PAYMENT", - CREATE_SERIVCE = "CREATE_SERIVCE", + CREATE_SERVICE = "CREATE_SERVICE", UNBLOCKING_SERVICE = "UNBLOCKING_SERVICE", ANSWER_TICKET = "ANSWER_TICKET", CREATE_INVOICE = "CREATE_INVOICE", @@ -13,7 +13,7 @@ export enum NotifSettingsEnum { export enum FarsiNotifSettingsEnum { ANNOUNCE_NOTIF = "اعلانات و اطلاعیه ها", BILL_PAYMENT = "پرداخت صورت حساب", - CREATE_SERIVCE = "ایجاد سرویس", + CREATE_SERVICE = "ایجاد سرویس", UNBLOCKING_SERVICE = "رفع انسداد سرویس", ANSWER_TICKET = "پاسخ تیکت", CREATE_INVOICE = "ایجاد صورت حساب", diff --git a/src/modules/settings/enums/user-settings.ts b/src/modules/settings/enums/user-settings.ts index c1a0c5e..97232bb 100644 --- a/src/modules/settings/enums/user-settings.ts +++ b/src/modules/settings/enums/user-settings.ts @@ -44,7 +44,7 @@ export const userSettings = [ }, }, { - title: NotifSettingsEnum.CREATE_SERIVCE, + title: NotifSettingsEnum.CREATE_SERVICE, fields: { sms: true, email: false, diff --git a/src/modules/settings/providers/user-settings.service.ts b/src/modules/settings/providers/user-settings.service.ts index a1d14f0..a7b9843 100644 --- a/src/modules/settings/providers/user-settings.service.ts +++ b/src/modules/settings/providers/user-settings.service.ts @@ -12,31 +12,31 @@ import { UserSettingsRepository } from "../repositories/user-settings.repository @Injectable() export class UserSettingsService { constructor(private readonly userSettingsRepository: UserSettingsRepository) {} + //*+******************************************* async createUserSettings(userId: string, queryRunner: QueryRunner) { const user = await queryRunner.manager.findOneBy(User, { id: userId, }); if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND); + for (const setting of userSettings) { const notifSetting = await queryRunner.manager.findOneBy(NotifSetting, { title: setting.title, }); - console.log({ notifSetting }); if (!notifSetting) throw new BadRequestException(SettingMessageEnum.NOTIF_NOT_FOUND); const userSetting = queryRunner.manager.create(UserSetting, { ...setting, notifSetting, user, }); - console.log({ userSetting }); await queryRunner.manager.save(userSetting); } return { message: CommonMessage.CREATED, }; } - + //*+******************************************* async getSettings(userId: string) { const settings = await this.userSettingsRepository.find({ where: { @@ -51,6 +51,7 @@ export class UserSettingsService { }); return { settings }; } + //*+******************************************* async updateUserSetting(settingId: string, updateDto: UpdateUserSettingsDto) { const setting = await this.userSettingsRepository.findOne({ diff --git a/src/modules/tickets/tickets.module.ts b/src/modules/tickets/tickets.module.ts index 04cb158..8862d5e 100644 --- a/src/modules/tickets/tickets.module.ts +++ b/src/modules/tickets/tickets.module.ts @@ -14,7 +14,7 @@ import { TicketMessageAttachment } from "./entities/ticket-message-attachment"; import { TicketMessageAttachmentsRepository } from "./repositories/ticket-message-attachment.repository"; @Module({ - imports: [TypeOrmModule.forFeature([Ticket, TicketCategory, TicketMessage, TicketMessageAttachment]), UsersModule], + imports: [TypeOrmModule.forFeature([Ticket, TicketCategory, TicketMessageAttachment, TicketMessage]), UsersModule], providers: [TicketsService, TicketsRepository, TicketCategoryRepository, TicketMessagesRepository, TicketMessageAttachmentsRepository], controllers: [TicketsController], exports: [TicketsService], diff --git a/src/modules/users/providers/users.service.ts b/src/modules/users/providers/users.service.ts index 7c8b417..7f8a39c 100644 --- a/src/modules/users/providers/users.service.ts +++ b/src/modules/users/providers/users.service.ts @@ -1,11 +1,11 @@ import { BadRequestException, Injectable } from "@nestjs/common"; import slugify from "slugify"; -import { DataSource, In, Not } from "typeorm"; +import { In, Not, QueryRunner } from "typeorm"; -// import { roles } from "../../../../database/seeders/role.seeder"; import { CommonMessage, UserMessage } from "../../../common/enums/message.enum"; import { CompleteRegistrationDto } from "../../auth/DTO/complete-register.dto"; import { UserSettingsService } from "../../settings/providers/user-settings.service"; +import { WalletsService } from "../../wallets/providers/wallets.service"; import { CheckValidityDTO } from "../DTO/check-validity.dto"; import { UpdateProfileDto } from "../DTO/update-profile.dto"; import { CreateUserGroupDto } from "../DTO/user-group.dto"; @@ -33,10 +33,10 @@ export class UsersService { }, }; constructor( - private userRepository: UserRepository, - private userGroupRepository: UserGroupRepository, + private readonly userRepository: UserRepository, + private readonly userGroupRepository: UserGroupRepository, private readonly userSettingsService: UserSettingsService, - private readonly dataSource: DataSource, + private readonly walletsService: WalletsService, ) {} /************************************************************ */ @@ -91,37 +91,22 @@ export class UsersService { /************************************************************ */ - async createUser(registerDto: CompleteRegistrationDto, hashedPassword: string): Promise { - const queryRunner = this.dataSource.createQueryRunner(); + async createUser(registerDto: CompleteRegistrationDto, hashedPassword: string, queryRunner: QueryRunner): Promise { + const role = await queryRunner.manager.findOne(Role, { where: { name: RoleEnum.USER } }); + if (!role) throw new BadRequestException(UserMessage.ROLE_NOT_FOUND); - await queryRunner.connect(); - await queryRunner.startTransaction(); + const user = queryRunner.manager.create(User, { + ...registerDto, + password: hashedPassword, + role, + }); + await queryRunner.manager.save(user); - try { - const role = await queryRunner.manager.findOne(Role, { - where: { name: RoleEnum.USER }, - }); - if (!role) throw new BadRequestException(UserMessage.ROLE_NOT_FOUND); + await this.userSettingsService.createUserSettings(user.id, queryRunner); - const user = queryRunner.manager.create(User, { - ...registerDto, - password: hashedPassword, - role, - }); - await queryRunner.manager.save(user); - - await this.userSettingsService.createUserSettings(user.id, queryRunner); - - await queryRunner.commitTransaction(); - - return user; - } catch (error) { - await queryRunner.rollbackTransaction(); - throw error; - } finally { - await queryRunner.release(); - } + await this.walletsService.createUserWallet(user.id, queryRunner); + return user; // const role = await this.roleRepository.findOneBy({ name: RoleEnum.USER }); // if (!role) throw new BadRequestException(UserMessage.ROLE_NOT_FOUND); diff --git a/src/modules/users/users.module.ts b/src/modules/users/users.module.ts index 67bbb5b..3491dfb 100644 --- a/src/modules/users/users.module.ts +++ b/src/modules/users/users.module.ts @@ -1,4 +1,4 @@ -import { Module, OnApplicationBootstrap } from "@nestjs/common"; +import { Module } from "@nestjs/common"; import { TypeOrmModule } from "@nestjs/typeorm"; import { Role } from "./entities/role.entity"; @@ -12,16 +12,12 @@ import { UsersController } from "./users.controller"; import { UserSetting } from "../settings/entities/user-setting.entity"; import { UserSettingsService } from "../settings/providers/user-settings.service"; import { UserSettingsRepository } from "../settings/repositories/user-settings.repository"; +import { WalletsModule } from "../wallets/wallets.module"; @Module({ - imports: [TypeOrmModule.forFeature([User, Role, UserGroup, UserSetting])], + imports: [TypeOrmModule.forFeature([User, Role, UserGroup, UserSetting]), WalletsModule], providers: [UsersService, UserRepository, RoleRepository, UserGroupRepository, UserSettingsRepository, UserSettingsService], controllers: [UsersController], exports: [UsersService], }) -export class UsersModule implements OnApplicationBootstrap { - // constructor(private userService: UsersService) {} - async onApplicationBootstrap() { - // await this.userService.roleSeeder(); - } -} +export class UsersModule {} diff --git a/src/modules/wallets/providers/wallets.service.ts b/src/modules/wallets/providers/wallets.service.ts index 335e78f..60b078b 100644 --- a/src/modules/wallets/providers/wallets.service.ts +++ b/src/modules/wallets/providers/wallets.service.ts @@ -1,5 +1,5 @@ import { BadRequestException, HttpException, Injectable, InternalServerErrorException } from "@nestjs/common"; -import { DataSource } from "typeorm"; +import { DataSource, QueryRunner } from "typeorm"; import { WalletMessage } from "../../../common/enums/message.enum"; import { PaymentsService } from "../../payments/providers/payments.service"; @@ -42,9 +42,14 @@ export class WalletsService { wallet.user.phone, ); - const payment = await this.paymentsService.createPaymentForUser(wallet.user, amount, gatewayData.reference, queryRunner); + console.log(gatewayData); + + const payment = await this.paymentsService.createPaymentForUser(wallet.user, amount, gatewayData.reference, gateway, queryRunner); + + await queryRunner.commitTransaction(); return { payment, + ...gatewayData, }; } catch (error) { await queryRunner.rollbackTransaction(); @@ -54,4 +59,12 @@ export class WalletsService { await queryRunner.release(); } } + + //*********************************** */ + async createUserWallet(userId: string, queryRunner: QueryRunner) { + const wallet = queryRunner.manager.create(Wallet, { balance: 0, user: { id: userId } }); + await queryRunner.manager.save(Wallet, wallet); + // + return wallet; + } }