From 6eea07729bf70fdabc6e9685d01f459a734afcd5 Mon Sep 17 00:00:00 2001 From: mahyargdz Date: Tue, 11 Mar 2025 12:33:00 +0330 Subject: [PATCH] fix: refresh token invalidate for logout --- src/common/enums/message.enum.ts | 2 + src/modules/auth/auth.controller.ts | 4 +- src/modules/auth/providers/auth.service.ts | 4 +- src/modules/auth/providers/tokens.service.ts | 7 +- .../invoices/DTO/update-invoice.dto.ts | 5 + src/modules/invoices/invoices.controller.ts | 8 ++ src/modules/invoices/invoices.module.ts | 3 +- .../providers/invoice-creation.service.ts | 0 .../invoices/providers/invoices.service.ts | 120 +++++++++++++----- 9 files changed, 115 insertions(+), 38 deletions(-) create mode 100644 src/modules/invoices/DTO/update-invoice.dto.ts delete mode 100644 src/modules/invoices/providers/invoice-creation.service.ts diff --git a/src/common/enums/message.enum.ts b/src/common/enums/message.enum.ts index 7bb5c10..d662556 100755 --- a/src/common/enums/message.enum.ts +++ b/src/common/enums/message.enum.ts @@ -452,6 +452,8 @@ export const enum InvoiceMessage { INVALID_DATE = "تاریخ وارد شده معتبر نیست", IS_RECURRING_MUST_BE_A_BOOLEAN = "وضعیت تکرار باید یک بولین باشد", RECURRING_PERIOD_REQUIRED = "دوره تکرار مورد نیاز است", + INVOICE_CAN_NOT_UPDATE = "صورت حساب قابل ویرایش نیست", + INVOICE_UPDATED = "صورت حساب با موفقیت به روز رسانی شد", } export const enum LearningMessage { diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index ee62845..e52621d 100755 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -96,8 +96,8 @@ export class AuthController { @ApiOperation({ summary: "logout the user" }) @HttpCode(HttpStatus.OK) @Post("logout") - logout(@Body() refreshTokenDto: RefreshTokenDto) { - return this.authService.logout(refreshTokenDto.refreshToken); + logout(@UserDec("id") userId: string) { + return this.authService.logout(userId); } //***************************** */ diff --git a/src/modules/auth/providers/auth.service.ts b/src/modules/auth/providers/auth.service.ts index 4a151de..4d71d52 100755 --- a/src/modules/auth/providers/auth.service.ts +++ b/src/modules/auth/providers/auth.service.ts @@ -224,8 +224,8 @@ export class AuthService { } //****************** */ - async logout(refreshToken: string) { - await this.tokensService.invalidateRefreshToken(refreshToken); + async logout(userId: string) { + await this.tokensService.invalidateRefreshToken(userId); return { message: AuthMessage.LOGOUT_SUCCESS, }; diff --git a/src/modules/auth/providers/tokens.service.ts b/src/modules/auth/providers/tokens.service.ts index c158ce8..e04d01e 100755 --- a/src/modules/auth/providers/tokens.service.ts +++ b/src/modules/auth/providers/tokens.service.ts @@ -125,9 +125,8 @@ export class TokensService { } /************************************************************ */ - async invalidateRefreshToken(oldRefreshToken: string) { - const token = await this.refreshTokensRepository.findOne({ where: { token: oldRefreshToken }, relations: { user: true } }); - - if (token) await this.refreshTokensRepository.delete({ id: token.id }); + async invalidateRefreshToken(userId: string) { + await this.refreshTokensRepository.delete({ user: { id: userId } }); + this.logger.log(`Successfully deleted all refresh tokens for user ${userId}`); } } diff --git a/src/modules/invoices/DTO/update-invoice.dto.ts b/src/modules/invoices/DTO/update-invoice.dto.ts new file mode 100644 index 0000000..7e0dc38 --- /dev/null +++ b/src/modules/invoices/DTO/update-invoice.dto.ts @@ -0,0 +1,5 @@ +import { PartialType } from "@nestjs/swagger"; + +import { CreateInvoiceDto } from "./create-invoice.dto"; + +export class UpdateInvoiceDto extends PartialType(CreateInvoiceDto) {} diff --git a/src/modules/invoices/invoices.controller.ts b/src/modules/invoices/invoices.controller.ts index bccb7dd..a9baa08 100755 --- a/src/modules/invoices/invoices.controller.ts +++ b/src/modules/invoices/invoices.controller.ts @@ -3,6 +3,7 @@ import { ApiOperation } from "@nestjs/swagger"; import { CreateInvoiceDto } from "./DTO/create-invoice.dto"; import { InvoicesSearchQueryDto, UserInvoicesSearchQueryDto } from "./DTO/invoices-search-query.dto"; +import { UpdateInvoiceDto } from "./DTO/update-invoice.dto"; import { InvoicesService } from "./providers/invoices.service"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; import { Pagination } from "../../common/decorators/pagination.decorator"; @@ -24,6 +25,13 @@ export class InvoicesController { return this.invoiceService.createInvoiceAdmin(createDto); } + @ApiOperation({ summary: "update an invoice ==> admin route" }) + @PermissionsDec(PermissionEnum.INVOICES) + @Patch(":id") + updateInvoice(@Param() paramDto: ParamDto, @Body() updateDto: UpdateInvoiceDto) { + return this.invoiceService.updateInvoiceAdmin(paramDto.id, updateDto); + } + @ApiOperation({ summary: "get all invoices ==> admin route" }) @PermissionsDec(PermissionEnum.INVOICES) @Pagination() diff --git a/src/modules/invoices/invoices.module.ts b/src/modules/invoices/invoices.module.ts index 7a47fa5..c2a7e74 100755 --- a/src/modules/invoices/invoices.module.ts +++ b/src/modules/invoices/invoices.module.ts @@ -17,6 +17,7 @@ import { UsersModule } from "../users/users.module"; import { UtilsModule } from "../utils/utils.module"; import { WalletsModule } from "../wallets/wallets.module"; import { InvoiceProcessor } from "./queue/invoice.processor"; +import { InvoiceItemsRepository } from "./repositories/invoice-items.repository"; @Module({ imports: [ @@ -27,7 +28,7 @@ import { InvoiceProcessor } from "./queue/invoice.processor"; UtilsModule, NotificationModule, ], - providers: [InvoicesService, InvoicesRepository, DiscountRepository, UsageDiscountRepository, InvoiceProcessor], + providers: [InvoicesService, InvoicesRepository, InvoiceItemsRepository, DiscountRepository, UsageDiscountRepository, InvoiceProcessor], controllers: [InvoicesController], exports: [InvoicesService], }) diff --git a/src/modules/invoices/providers/invoice-creation.service.ts b/src/modules/invoices/providers/invoice-creation.service.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/modules/invoices/providers/invoices.service.ts b/src/modules/invoices/providers/invoices.service.ts index df784d1..f836d2a 100755 --- a/src/modules/invoices/providers/invoices.service.ts +++ b/src/modules/invoices/providers/invoices.service.ts @@ -23,9 +23,11 @@ import { WalletsService } from "../../wallets/providers/wallets.service"; import { INVOICE } from "../constants"; import { CreateInvoiceDto } from "../DTO/create-invoice.dto"; import { InvoicesSearchQueryDto, UserInvoicesSearchQueryDto } from "../DTO/invoices-search-query.dto"; +import { UpdateInvoiceDto } from "../DTO/update-invoice.dto"; import { Invoice } from "../entities/invoice.entity"; import { RecurringPeriodEnum } from "../enums/invoice-recurring-period.enum"; import { InvoiceStatus } from "../enums/invoice-status.enum"; +import { InvoiceItemsRepository } from "../repositories/invoice-items.repository"; import { InvoicesRepository } from "../repositories/invoices.repository"; @Injectable() @@ -36,6 +38,7 @@ export class InvoicesService { @InjectQueue(INVOICE.INVOICE_QUEUE_NAME) private readonly invoiceQueue: Queue, private readonly notificationsService: NotificationsService, private readonly invoiceRepository: InvoicesRepository, + private readonly invoiceItemsRepository: InvoiceItemsRepository, private readonly usersService: UsersService, private readonly walletsService: WalletsService, private readonly otpService: OTPService, @@ -65,7 +68,7 @@ export class InvoicesService { const dueDate = dayjs().add(INVOICE.DUEDATE, "day").toDate(); - const invoice = queryRunner.manager.create(Invoice, { + const invoice = queryRunner.manager.create(this.invoiceRepository.target, { user: { id: createDto.userId }, totalPrice: totalPrice.add(tax).toNumber(), items: invoiceItems, @@ -73,7 +76,7 @@ export class InvoicesService { dueDate, }); - await queryRunner.manager.save(Invoice, invoice); + await queryRunner.manager.save(this.invoiceRepository.target, invoice); const user = await this.usersService.findOneByIdWithQueryRunner(createDto.userId, queryRunner); @@ -92,33 +95,7 @@ export class InvoicesService { queryRunner, ); - //add this to queue for billing reminder - await this.invoiceQueue.add( - INVOICE.INVOICE_REMINDER_JOB_NAME, - { invoiceId: invoice.id }, - { - delay: dayjs(invoice.dueDate).subtract(INVOICE.DAYS_BEFORE_OVERDUE, "day").diff(dayjs()), - attempts: INVOICE.INVOICE_REMINDER_JOB_ATTEMPTS, - backoff: { type: "exponential", delay: INVOICE.INVOICE_REMINDER_JOB_BACKOFF }, - }, - ); - //add to queue for recurring invoice - if (createDto.isRecurring && createDto.recurringPeriod) { - await this.invoiceQueue.add( - INVOICE.INVOICE_RECURRING_JOB_NAME, - { - invoiceId: invoice.id, - adminCreated: true, - }, - { - delay: await this.calculateRecurringDelay(createDto.recurringPeriod), - attempts: INVOICE.INVOICE_RECURRING_JOB_ATTEMPTS, - backoff: { type: "exponential", delay: INVOICE.INVOICE_REMINDER_JOB_BACKOFF }, - }, - ); - - this.logger.log(`Scheduled recurring invoice for user ${createDto.userId} with interval ${createDto.recurringPeriod}`); - } + await this.scheduleInvoiceJobs(invoice, createDto); await queryRunner.commitTransaction(); return { @@ -135,6 +112,59 @@ export class InvoicesService { //********************************** */ + //TODO: fix this + async updateInvoiceAdmin(invoiceId: string, updateDto: UpdateInvoiceDto) { + const queryRunner = this.dataSource.createQueryRunner(); + + try { + await queryRunner.connect(); + await queryRunner.startTransaction(); + + const invoice = await queryRunner.manager.findOne(this.invoiceRepository.target, { + where: { id: invoiceId }, + relations: { items: true }, + }); + + if (!invoice) throw new BadRequestException(InvoiceMessage.NOT_FOUND_BY_ID); + + if (invoice.status !== InvoiceStatus.PENDING) throw new BadRequestException(InvoiceMessage.INVOICE_CAN_NOT_UPDATE); + + if (updateDto.items) { + const invoiceItemsData = updateDto.items.map((item) => ({ + name: item.name, + count: item.count, + unitPrice: item.unitPrice, + discount: item.discount || 0, + totalPrice: item.unitPrice * item.count - (item.unitPrice * item.count * item.discount) / 100, + })); + // calculate total price and tax + const totalPrice = invoiceItemsData.reduce((sum, item) => new Decimal(item.totalPrice).add(sum), new Decimal(0)); + const tax = totalPrice.mul(0.1); + invoice.totalPrice = totalPrice.add(tax); + invoice.tax = tax; + // + const invoiceItems = invoiceItemsData.map((itemData) => + queryRunner.manager.create(this.invoiceItemsRepository.target, { + ...itemData, + invoice: invoice, + }), + ); + invoice.items = invoiceItems; + } + + if (updateDto.userId) { + const user = await this.usersService.findOneByIdWithQueryRunner(updateDto.userId, queryRunner); + invoice.user = user; + } + } catch (error) { + await queryRunner.rollbackTransaction(); + throw error; + } finally { + await queryRunner.release(); + } + } + //********************************** */ + async approveInvoiceRequest(invoiceId: string, userId: string) { const { user } = await this.usersService.findOneById(userId); @@ -497,6 +527,38 @@ export class InvoicesService { return delayMs; } + //*********************************** */ + private async scheduleInvoiceJobs(invoice: Invoice, createDto?: CreateInvoiceDto) { + // Add to queue for billing reminder + await this.invoiceQueue.add( + INVOICE.INVOICE_REMINDER_JOB_NAME, + { invoiceId: invoice.id }, + { + delay: dayjs(invoice.dueDate).subtract(INVOICE.DAYS_BEFORE_OVERDUE, "day").diff(dayjs()), + attempts: INVOICE.INVOICE_REMINDER_JOB_ATTEMPTS, + backoff: { type: "exponential", delay: INVOICE.INVOICE_REMINDER_JOB_BACKOFF }, + }, + ); + + // Add to queue for recurring invoice if applicable + if (createDto?.isRecurring && createDto?.recurringPeriod) { + await this.invoiceQueue.add( + INVOICE.INVOICE_RECURRING_JOB_NAME, + { + invoiceId: invoice.id, + adminCreated: true, + }, + { + delay: await this.calculateRecurringDelay(createDto.recurringPeriod), + attempts: INVOICE.INVOICE_RECURRING_JOB_ATTEMPTS, + backoff: { type: "exponential", delay: INVOICE.INVOICE_REMINDER_JOB_BACKOFF }, + }, + ); + + this.logger.log(`Scheduled recurring invoice for user ${createDto.userId} with interval ${createDto.recurringPeriod}`); + } + } + // async applyDiscount(invoiceId: string, applyDiscountDto: ApplyDiscountDto, userId: string) { // const invoice = await this.invoiceRepository.findOneBy({ id: invoiceId, status: InvoiceStatus.PENDING }); // if (!invoice) throw new BadRequestException(InvoiceMessage.NOT_FOUND_BY_ID);