chore: add ticket queue
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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],
|
||||
|
||||
Reference in New Issue
Block a user