chore: add ticket queue

This commit is contained in:
Mahyargdz
2025-05-14 09:49:12 +03:30
parent 1d8e8ec07f
commit d2af460de3
5 changed files with 109 additions and 3 deletions
+7 -1
View File
@@ -23,7 +23,13 @@ import { WalletsModule } from "../wallets/wallets.module";
@Module({
imports: [
LoggerModule,
BullModule.registerQueue({ name: INVOICE.QUEUE_NAME }),
BullModule.registerQueue({
name: INVOICE.QUEUE_NAME,
defaultJobOptions: {
removeOnComplete: true,
removeOnFail: true,
},
}),
TypeOrmModule.forFeature([Invoice, InvoiceItem, Discount, UsageDiscount]),
UsersModule,
WalletsModule,
+8
View File
@@ -1 +1,9 @@
export const REFERRAL_STRATEGY = "referralStrategy";
export const TICKET = {
QUEUE_NAME: "ticket",
AUTO_CLOSE_JOB_NAME: "auto-close-inactive",
AUTO_CLOSE_DAYS: 10,
AUTO_CLOSE_PRIORITY: 1,
AUTO_CLOSE_REPEAT_PATTERN: "0 1 * * *",
};
@@ -1,7 +1,10 @@
import { BadRequestException, HttpException, Injectable, InternalServerErrorException, Logger } from "@nestjs/common";
import { InjectQueue } from "@nestjs/bullmq";
import { BadRequestException, HttpException, Injectable, InternalServerErrorException, Logger, OnModuleInit } from "@nestjs/common";
import { Queue } from "bullmq";
import dayjs from "dayjs";
import { DataSource, MoreThanOrEqual, Not, QueryRunner } from "typeorm";
import { TICKET } from "../constant";
import { ReferralService } from "./referral.service";
import { ParamDto } from "../../../common/DTO/param.dto";
import { CommonMessage, TicketMessageEnum, UserMessage } from "../../../common/enums/message.enum";
@@ -29,10 +32,11 @@ import { TicketMessagesRepository } from "../repositories/tickets-message.reposi
import { TicketsRepository } from "../repositories/tickets.repository";
@Injectable()
export class TicketsService {
export class TicketsService implements OnModuleInit {
private readonly logger = new Logger(TicketsService.name);
constructor(
@InjectQueue(TICKET.QUEUE_NAME) private readonly ticketQueue: Queue,
private readonly ticketsRepository: TicketsRepository,
private readonly ticketsCategoryRepository: TicketCategoryRepository,
private readonly ticketMessagesRepository: TicketMessagesRepository,
@@ -44,6 +48,10 @@ export class TicketsService {
private readonly supportPlansService: SupportPlansService,
) {}
async onModuleInit() {
await this.scheduleAutoCloseJob();
}
//******************************** */
async getTicketCategories() {
const ticketCategories = await this.ticketsCategoryRepository.find({});
@@ -466,4 +474,17 @@ export class TicketsService {
});
}
}
//***************************** */
async scheduleAutoCloseJob() {
await this.ticketQueue.add(
TICKET.AUTO_CLOSE_JOB_NAME,
{},
{
repeat: { pattern: TICKET.AUTO_CLOSE_REPEAT_PATTERN },
priority: TICKET.AUTO_CLOSE_PRIORITY,
},
);
this.logger.log("Scheduled auto-close-inactive job for tickets");
}
}
@@ -0,0 +1,64 @@
import { Processor } from "@nestjs/bullmq";
import { Job } from "bullmq";
import dayjs from "dayjs";
import { DataSource } from "typeorm";
import { WorkerProcessor } from "../../../common/queues/worker.processor";
import { LoggerService } from "../../logger/logger.service";
import { TICKET } from "../constant";
import { Ticket } from "../entities/ticket.entity";
import { TicketStatus } from "../enums/ticket-status.enum";
@Processor(TICKET.QUEUE_NAME)
export class TicketProcessor extends WorkerProcessor {
constructor(
private readonly dataSource: DataSource,
protected readonly logger: LoggerService,
) {
super();
}
async process(job: Job, token?: string) {
this.logger.log(job);
switch (job.name) {
case TICKET.AUTO_CLOSE_JOB_NAME:
return this.autoCloseInactiveTickets(job, token);
default:
this.logger.error(`Unknown job name: ${job.name}`);
return;
}
}
private async autoCloseInactiveTickets(job: Job, token?: string) {
this.logger.log(`Running auto-close for inactive tickets. ${job.id} ${token ? `Token: ${token}` : ""}`);
const queryRunner = this.dataSource.createQueryRunner();
try {
await queryRunner.connect();
await queryRunner.startTransaction();
// Find tickets that are not closed and have no admin/user message in the last 10 days
const tickets = await queryRunner.manager.find(Ticket, {
where: { status: TicketStatus.PENDING },
relations: { messages: true, assignedTo: true, user: true },
});
const now = dayjs();
for (const ticket of tickets) {
if (!ticket.messages || ticket.messages.length === 0) continue;
// Find the last message
const lastMessage = ticket.messages.reduce((a, b) => (dayjs(a.createdAt).isAfter(dayjs(b.createdAt)) ? a : b));
// If last message is older than 10 days, close the ticket
if (now.diff(dayjs(lastMessage.createdAt), "day") >= TICKET.AUTO_CLOSE_DAYS) {
ticket.status = TicketStatus.CLOSED;
await queryRunner.manager.save(Ticket, ticket);
this.logger.log(`Ticket ${ticket.id} closed due to inactivity.`);
}
}
await queryRunner.commitTransaction();
} catch (error) {
this.logger.error(`Failed to auto-close tickets:`, error);
await queryRunner.rollbackTransaction();
throw error;
} finally {
await queryRunner.release();
}
}
}
+7
View File
@@ -1,6 +1,8 @@
import { BullModule } from "@nestjs/bullmq";
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { TICKET } from "./constant";
import { TicketCategory } from "./entities/ticket-category.entity";
import { TicketMessage } from "./entities/ticket-message.entity";
import { Ticket } from "./entities/ticket.entity";
@@ -19,14 +21,18 @@ import { ReferralService } from "./providers/referral.service";
import { GroupStrategy } from "./strategies/group.strategy";
import { RoundRobinStrategy } from "./strategies/round-robin.strategy";
import { SupportPlansModule } from "../support-plans/support-plans.module";
import { TicketProcessor } from "./queue/ticket.processor";
import { LoggerModule } from "../logger/logger.module";
@Module({
imports: [
TypeOrmModule.forFeature([Ticket, TicketCategory, TicketMessageAttachment, TicketMessage]),
BullModule.registerQueue({ name: TICKET.QUEUE_NAME }),
UsersModule,
DanakServicesModule,
NotificationModule,
SupportPlansModule,
LoggerModule,
],
providers: [
GroupStrategy,
@@ -42,6 +48,7 @@ import { SupportPlansModule } from "../support-plans/support-plans.module";
TicketCategoryRepository,
TicketMessagesRepository,
TicketMessageAttachmentsRepository,
TicketProcessor,
],
controllers: [TicketsController],
exports: [TicketsService],