Files
dmail-api/src/modules/email/queue/email-bulk-action.processor.ts
T
2025-07-27 13:25:10 +03:30

171 lines
5.9 KiB
TypeScript

import { Processor, WorkerHost } from "@nestjs/bullmq";
import { Logger } from "@nestjs/common";
import { Job } from "bullmq";
import { firstValueFrom } from "rxjs";
import { EmailMessage } from "../../../common/enums/message.enum";
import { MailServerService } from "../../mail-server/services/mail-server.service";
import { EMAIL_QUEUE_CONSTANTS } from "../constants/email-events.constant";
import { IEmailBulkActionJob } from "../interfaces/email.interface";
import { EmailService } from "../services/email.service";
@Processor(EMAIL_QUEUE_CONSTANTS.EMAIL_BULK_ACTION_QUEUE)
export class EmailBulkActionProcessor extends WorkerHost {
private readonly logger = new Logger(EmailBulkActionProcessor.name);
constructor(
private readonly mailServerService: MailServerService,
private readonly emailService: EmailService,
) {
super();
}
async process(job: Job<IEmailBulkActionJob>) {
const { name, data } = job;
try {
switch (name) {
case EMAIL_QUEUE_CONSTANTS.EMAIL_BULK_ACTION_SEEN_ALL:
this.logger.debug(`Processing bulk action for: ${data.wildduckUserId}`);
await this.markAllMessagesAsRead(data.wildduckUserId, data.mailboxId);
break;
case EMAIL_QUEUE_CONSTANTS.EMAIL_BULK_ACTION_EMPTY_TRASH:
this.logger.debug(`Processing bulk action for: ${data.wildduckUserId}`);
await this.emptyTrash(data.wildduckUserId, data.mailboxId);
break;
case EMAIL_QUEUE_CONSTANTS.EMAIL_BULK_ACTION_EMPTY_SPAM:
this.logger.debug(`Processing bulk action for: ${data.wildduckUserId}`);
await this.emptySpam(data.wildduckUserId, data.mailboxId);
break;
default:
this.logger.warn(`Unknown job type: ${name}`);
throw new Error(`Unknown job type: ${name}`);
}
} catch (error) {
this.logger.error(`Failed to process bulk action: ${error}`);
throw error;
}
}
//==============================================
private async markAllMessagesAsRead(wildduckUserId: string, mailboxId: string) {
this.logger.log(`Marking all messages as read for user ${wildduckUserId} in mailbox ${mailboxId}`);
try {
// Get all unread messages from the mailbox with a reasonable limit
const limit = EMAIL_QUEUE_CONSTANTS.EMAIL_BULK_ACTION_BATCH_SIZE; // Process in batches to avoid memory issues
let totalMarked = 0;
let hasMore = true;
let next: string | undefined;
const results = {
successful: [] as number[],
failed: [] as { messageId: number; error: string }[],
totalMarked: 0,
};
while (hasMore) {
// Fetch unread messages in batches
const query: Record<string, unknown> = {
limit,
order: "desc",
unseen: true, // Only get unread messages
};
if (next) {
query.next = next;
}
const response = await firstValueFrom(this.mailServerService.messages.listMessages(wildduckUserId, mailboxId, query));
if (!response.results || response.results.length === 0) {
hasMore = false;
break;
}
// Mark each message as seen
for (const message of response.results) {
try {
await this.emailService.markMessageAsSeen(wildduckUserId, message.id, mailboxId);
results.successful.push(message.id);
totalMarked++;
} catch (error) {
this.logger.error(`Failed to mark message ${message.id} as read: ${error}`);
results.failed.push({
messageId: message.id,
error: error instanceof Error ? error.message : "Unknown error",
});
}
}
// Check if there are more messages
next = typeof response.nextCursor === "string" ? response.nextCursor : undefined;
hasMore = !!next && response.results.length === limit;
}
results.totalMarked = totalMarked;
this.logger.log(`Marked ${totalMarked} messages as read for user ${wildduckUserId} in mailbox ${mailboxId}, ${results.failed.length} failed`);
return {
success: true,
message: EmailMessage.ALL_MESSAGES_MARKED_AS_READ_SUCCESSFULLY,
...results,
};
} catch (error) {
this.logger.error(`Failed to mark all messages as read for user ${wildduckUserId} in mailbox ${mailboxId}:`, error);
throw error;
}
}
//==============================================
private async emptyTrash(wildduckUserId: string, mailboxId: string) {
this.logger.log(`Emptying trash for user ${wildduckUserId} in mailbox ${mailboxId}`);
try {
const { result } = await this.emailService.deleteAllMessages(wildduckUserId, mailboxId);
if (!result.success) {
this.logger.error(`Failed to empty trash for user ${wildduckUserId} in mailbox ${mailboxId}:`, result);
throw new Error(result.success);
}
return {
success: true,
message: EmailMessage.TRASH_EMPTYED_SUCCESSFULLY,
result,
};
} catch (error) {
this.logger.error(`Failed to empty trash for user ${wildduckUserId} in mailbox ${mailboxId}:`, error);
throw error;
}
}
//==============================================
private async emptySpam(wildduckUserId: string, mailboxId: string) {
this.logger.log(`Emptying spam for user ${wildduckUserId} in mailbox ${mailboxId}`);
try {
const { result } = await this.emailService.deleteAllMessages(wildduckUserId, mailboxId);
if (!result.success) {
this.logger.error(`Failed to empty spam for user ${wildduckUserId} in mailbox ${mailboxId}:`, result);
throw new Error(result.success);
}
return {
success: true,
message: EmailMessage.SPAM_EMPTYED_SUCCESSFULLY,
result,
};
} catch (error) {
this.logger.error(`Failed to empty spam for user ${wildduckUserId} in mailbox ${mailboxId}:`, error);
throw error;
}
}
}