chore: update webscoket connetion and polling service

This commit is contained in:
mahyargdz
2025-07-30 09:37:11 +03:30
parent 5315fae914
commit f46c2ced99
13 changed files with 140 additions and 113 deletions
@@ -49,13 +49,13 @@ export const EMAIL_QUEUE_CONSTANTS = {
EMAIL_BULK_ACTION_DELAY: 1000,
EMAIL_BULK_ACTION_BATCH_SIZE: 250,
EMAIL_BULK_ACTION_ATTEMPTS: 3,
EMAIL_BULK_ACTION_BACKOFF_DELAY: 2000, // 2 seconds
EMAIL_BULK_ACTION_BACKOFF_DELAY: 2000,
EMAIL_BULK_ACTION_PRIORITY: 1,
//
EMAIL_POLLING_INTERVAL: 30000, // 30 seconds
EMAIL_POLLING_REMOVE_ON_COMPLETE: 5, // Keep last 5 completed jobs
EMAIL_POLLING_REMOVE_ON_FAIL: 10, // Keep last 10 failed jobs
EMAIL_POLLING_ATTEMPTS: 3, // Retry failed jobs 3 times
EMAIL_POLLING_BACKOFF_DELAY: 2000, // 2 seconds
EMAIL_POLLING_INTERVAL: 3000,
EMAIL_POLLING_REMOVE_ON_COMPLETE: true,
EMAIL_POLLING_REMOVE_ON_FAIL: 10,
EMAIL_POLLING_ATTEMPTS: 3,
EMAIL_POLLING_BACKOFF_DELAY: 2000,
EMAIL_POLLING_PRIORITY: 1,
} as const;
+6 -6
View File
@@ -115,24 +115,24 @@ export class EmailController {
@ApiResponse({ status: 206, description: "Some messages marked as read, some failed" })
@ApiResponse({ status: 404, description: "Mailbox not found" })
@ApiResponse({ status: 403, description: "Not authorized to mark messages as read" })
markAllMessagesAsRead(@UserDec("wildduckUserId") userId: string, @Query() query: MailboxIdQueryDto) {
return this.emailService.markAllMessagesAsRead(userId, query.mailbox);
markAllMessagesAsRead(@UserDec("wildduckUserId") wildduckUserId: string, @UserDec("id") userId: string, @Query() query: MailboxIdQueryDto) {
return this.emailService.markAllMessagesAsRead(wildduckUserId, userId, query.mailbox);
}
@Patch("messages/empty-trash")
@ApiOperation({ summary: "Empty trash" })
@ApiResponse({ status: 200, description: "Trash emptied successfully" })
@ApiResponse({ status: 404, description: "Trash not found" })
emptyTrash(@UserDec("wildduckUserId") userId: string, @Query() query: MailboxIdQueryDto) {
return this.emailService.emptyTrash(userId, query.mailbox);
emptyTrash(@UserDec("wildduckUserId") wildduckUserId: string, @UserDec("id") userId: string, @Query() query: MailboxIdQueryDto) {
return this.emailService.emptyTrash(wildduckUserId, userId, query.mailbox);
}
@Patch("messages/empty-spam")
@ApiOperation({ summary: "Empty spam" })
@ApiResponse({ status: 200, description: "Spam emptied successfully" })
@ApiResponse({ status: 404, description: "Spam not found" })
emptySpam(@UserDec("wildduckUserId") userId: string, @Query() query: MailboxIdQueryDto) {
return this.emailService.emptySpam(userId, query.mailbox);
emptySpam(@UserDec("wildduckUserId") wildduckUserId: string, @UserDec("id") userId: string, @Query() query: MailboxIdQueryDto) {
return this.emailService.emptySpam(wildduckUserId, userId, query.mailbox);
}
@Get("messages/inbox")
+4
View File
@@ -17,6 +17,10 @@ import { User } from "../users/entities/user.entity";
imports: [
BullModule.registerQueue({
name: EMAIL_QUEUE_CONSTANTS.EMAIL_BULK_ACTION_QUEUE,
defaultJobOptions: {
removeOnComplete: true,
removeOnFail: false,
},
}),
MailServerModule,
TemplatesModule,
@@ -1,4 +1,5 @@
export interface IEmailBulkActionJob {
wildduckUserId: string;
userId: string;
mailboxId: string;
}
@@ -31,17 +31,17 @@ export class EmailBulkActionProcessor extends WorkerHost {
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);
await this.markAllMessagesAsRead(data.wildduckUserId, data.userId, 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);
await this.emptyTrash(data.wildduckUserId, data.userId, 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);
await this.emptySpam(data.wildduckUserId, data.userId, data.mailboxId);
break;
default:
@@ -55,12 +55,11 @@ export class EmailBulkActionProcessor extends WorkerHost {
}
//==============================================
private async markAllMessagesAsRead(wildduckUserId: string, mailboxId: string) {
private async markAllMessagesAsRead(wildduckUserId: string, userId: 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
const limit = EMAIL_QUEUE_CONSTANTS.EMAIL_BULK_ACTION_BATCH_SIZE;
let totalMarked = 0;
let hasMore = true;
let next: string | undefined;
@@ -72,11 +71,10 @@ export class EmailBulkActionProcessor extends WorkerHost {
};
while (hasMore) {
// Fetch unread messages in batches
const query: Record<string, unknown> = {
limit,
order: "desc",
unseen: true, // Only get unread messages
unseen: true,
};
if (next) {
@@ -90,7 +88,6 @@ export class EmailBulkActionProcessor extends WorkerHost {
break;
}
// Mark each message as seen
for (const message of response.results) {
try {
await this.emailService.markMessageAsSeen(wildduckUserId, message.id, mailboxId);
@@ -105,7 +102,6 @@ export class EmailBulkActionProcessor extends WorkerHost {
}
}
// Check if there are more messages
next = typeof response.nextCursor === "string" ? response.nextCursor : undefined;
hasMore = !!next && response.results.length === limit;
}
@@ -113,12 +109,14 @@ export class EmailBulkActionProcessor extends WorkerHost {
results.totalMarked = totalMarked;
await this.emailNotificationService.notifyEmailRead({
userId: wildduckUserId,
userId,
messageId: results.successful[0],
mailboxId,
});
this.logger.log(`Marked ${totalMarked} messages as read for user ${wildduckUserId} in mailbox ${mailboxId}, ${results.failed.length} failed`);
this.logger.verbose(
`Marked ${totalMarked} messages as read for user ${wildduckUserId} in mailbox ${mailboxId}, ${results.failed.length} failed`,
);
return {
success: true,
@@ -133,7 +131,7 @@ export class EmailBulkActionProcessor extends WorkerHost {
//==============================================
private async emptyTrash(wildduckUserId: string, mailboxId: string) {
private async emptyTrash(wildduckUserId: string, userId: string, mailboxId: string) {
this.logger.log(`Emptying trash for user ${wildduckUserId} in mailbox ${mailboxId}`);
try {
@@ -145,7 +143,7 @@ export class EmailBulkActionProcessor extends WorkerHost {
}
await this.emailNotificationService.notifyEmailDeleted({
userId: wildduckUserId,
userId,
messageId: randomInt(1, 500),
mailboxId,
});
@@ -162,7 +160,7 @@ export class EmailBulkActionProcessor extends WorkerHost {
}
//==============================================
private async emptySpam(wildduckUserId: string, mailboxId: string) {
private async emptySpam(wildduckUserId: string, userId: string, mailboxId: string) {
this.logger.log(`Emptying spam for user ${wildduckUserId} in mailbox ${mailboxId}`);
try {
@@ -174,7 +172,7 @@ export class EmailBulkActionProcessor extends WorkerHost {
}
await this.emailNotificationService.notifyEmailDeleted({
userId: wildduckUserId,
userId,
messageId: randomInt(1, 500),
mailboxId,
});
+45 -15
View File
@@ -88,7 +88,7 @@ export class EmailService {
});
return {
message: EmailMessage.EMAIL_SENDING_SUCCESS,
message: sendEmailDto.isDraft ? EmailMessage.SAVE_DRAFT_SUCCESS : EmailMessage.EMAIL_SENDING_SUCCESS,
messageId: response.message.id,
queueId: response.message.queueId,
};
@@ -109,15 +109,18 @@ export class EmailService {
async searchMessages(wildduckUserId: string, query: SearchMessagesQueryDto) {
const wildDuckQuery: SearchMessagesMailServerQueryDto = {
...query,
limit: query.limit || 20,
limit: 100,
page: query.page || 1,
order: query.order || "desc",
searchable: true,
query: query.search,
from: query.from || query.search,
// mailbox: query.mailbox,
// subject: query.subject || query.search,
// from: query.from || query.search,
// to: query.to || query.search,
};
delete wildDuckQuery?.mailbox;
delete wildDuckQuery.search;
const response = await firstValueFrom(this.mailServerService.messages.searchMessages(wildduckUserId, wildDuckQuery));
@@ -283,11 +286,12 @@ export class EmailService {
}
//==============================================
async markAllMessagesAsRead(wildduckUserId: string, mailboxId: string) {
async markAllMessagesAsRead(wildduckUserId: string, userId: string, mailboxId: string) {
await this.emailBulkActionQueue.add(
EMAIL_QUEUE_CONSTANTS.EMAIL_BULK_ACTION_SEEN_ALL,
{
wildduckUserId,
userId,
mailboxId,
},
{
@@ -308,11 +312,24 @@ export class EmailService {
}
//==============================================
async emptyTrash(wildduckUserId: string, mailboxId: string) {
await this.emailBulkActionQueue.add(EMAIL_QUEUE_CONSTANTS.EMAIL_BULK_ACTION_EMPTY_TRASH, {
wildduckUserId,
mailboxId,
});
async emptyTrash(wildduckUserId: string, userId: string, mailboxId: string) {
await this.emailBulkActionQueue.add(
EMAIL_QUEUE_CONSTANTS.EMAIL_BULK_ACTION_EMPTY_TRASH,
{
wildduckUserId,
userId,
mailboxId,
},
{
delay: EMAIL_QUEUE_CONSTANTS.EMAIL_BULK_ACTION_DELAY,
priority: EMAIL_QUEUE_CONSTANTS.EMAIL_BULK_ACTION_PRIORITY,
attempts: EMAIL_QUEUE_CONSTANTS.EMAIL_BULK_ACTION_ATTEMPTS,
backoff: {
type: "exponential",
delay: EMAIL_QUEUE_CONSTANTS.EMAIL_BULK_ACTION_BACKOFF_DELAY,
},
},
);
return {
success: true,
@@ -321,11 +338,24 @@ export class EmailService {
}
//==============================================
async emptySpam(wildduckUserId: string, mailboxId: string) {
await this.emailBulkActionQueue.add(EMAIL_QUEUE_CONSTANTS.EMAIL_BULK_ACTION_EMPTY_SPAM, {
wildduckUserId,
mailboxId,
});
async emptySpam(wildduckUserId: string, userId: string, mailboxId: string) {
await this.emailBulkActionQueue.add(
EMAIL_QUEUE_CONSTANTS.EMAIL_BULK_ACTION_EMPTY_SPAM,
{
wildduckUserId,
userId,
mailboxId,
},
{
delay: EMAIL_QUEUE_CONSTANTS.EMAIL_BULK_ACTION_DELAY,
priority: EMAIL_QUEUE_CONSTANTS.EMAIL_BULK_ACTION_PRIORITY,
attempts: EMAIL_QUEUE_CONSTANTS.EMAIL_BULK_ACTION_ATTEMPTS,
backoff: {
type: "exponential",
delay: EMAIL_QUEUE_CONSTANTS.EMAIL_BULK_ACTION_BACKOFF_DELAY,
},
},
);
return {
success: true,