update: the email notif service
This commit is contained in:
@@ -5,10 +5,10 @@ import { firstValueFrom } from "rxjs";
|
|||||||
|
|
||||||
import { WorkerProcessor } from "../../../common/queues/worker.processor";
|
import { WorkerProcessor } from "../../../common/queues/worker.processor";
|
||||||
import { EMAIL_QUEUE_CONSTANTS } from "../../email/constants/email-events.constant";
|
import { EMAIL_QUEUE_CONSTANTS } from "../../email/constants/email-events.constant";
|
||||||
import { UserMailboxIds } from "../../email/interfaces/user-mailbox.interface";
|
|
||||||
import { EmailGateway } from "../../email-gateway/email.gateway";
|
import { EmailGateway } from "../../email-gateway/email.gateway";
|
||||||
import { MailServerService } from "../../mail-server/services/mail-server.service";
|
import { MailServerService } from "../../mail-server/services/mail-server.service";
|
||||||
import { EmailPollingJobData } from "../interfaces/email-polling-job.interface";
|
import { EmailPollingJobData } from "../interfaces/email-polling-job.interface";
|
||||||
|
import { EmailNotificationService } from "../services/email-notification.service";
|
||||||
import { MailboxResolverService } from "../services/mailbox-resolver.service";
|
import { MailboxResolverService } from "../services/mailbox-resolver.service";
|
||||||
|
|
||||||
@Processor(EMAIL_QUEUE_CONSTANTS.EMAIL_POLLING_QUEUE)
|
@Processor(EMAIL_QUEUE_CONSTANTS.EMAIL_POLLING_QUEUE)
|
||||||
@@ -16,6 +16,7 @@ export class EmailPollingProcessor extends WorkerProcessor {
|
|||||||
protected readonly logger = new Logger(EmailPollingProcessor.name);
|
protected readonly logger = new Logger(EmailPollingProcessor.name);
|
||||||
constructor(
|
constructor(
|
||||||
private readonly emailGateway: EmailGateway,
|
private readonly emailGateway: EmailGateway,
|
||||||
|
private readonly emailNotificationService: EmailNotificationService,
|
||||||
private readonly mailboxResolverService: MailboxResolverService,
|
private readonly mailboxResolverService: MailboxResolverService,
|
||||||
private readonly mailServerService: MailServerService,
|
private readonly mailServerService: MailServerService,
|
||||||
) {
|
) {
|
||||||
@@ -50,9 +51,9 @@ export class EmailPollingProcessor extends WorkerProcessor {
|
|||||||
this.logger.log(`Found ${newEmails.length} new emails for user ${wildduckUserId}`);
|
this.logger.log(`Found ${newEmails.length} new emails for user ${wildduckUserId}`);
|
||||||
|
|
||||||
const lastEmail = newEmails[newEmails.length - 1];
|
const lastEmail = newEmails[newEmails.length - 1];
|
||||||
await this.emailGateway.notifyNewEmail(userId, {
|
await this.emailNotificationService.notifyNewEmail({
|
||||||
wildduckUserId,
|
|
||||||
userId,
|
userId,
|
||||||
|
wildduckUserId,
|
||||||
messageId: lastEmail.id,
|
messageId: lastEmail.id,
|
||||||
date: lastEmail.date || new Date().toISOString(),
|
date: lastEmail.date || new Date().toISOString(),
|
||||||
from: lastEmail.from,
|
from: lastEmail.from,
|
||||||
@@ -65,16 +66,6 @@ export class EmailPollingProcessor extends WorkerProcessor {
|
|||||||
hasAttachments: !!(lastEmail.attachments && Array.isArray(lastEmail.attachments) && lastEmail.attachments.length > 0),
|
hasAttachments: !!(lastEmail.attachments && Array.isArray(lastEmail.attachments) && lastEmail.attachments.length > 0),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update unread count
|
|
||||||
const unreadStats = await this.getUnreadCounts(wildduckUserId, mailboxIds);
|
|
||||||
await this.emailGateway.notifyUnreadCountUpdate(userId, {
|
|
||||||
userId,
|
|
||||||
totalUnread: unreadStats.total,
|
|
||||||
mailboxCounts: unreadStats.mailboxCounts,
|
|
||||||
count: unreadStats.total,
|
|
||||||
timestamp: now.toISOString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
this.logger.log(`Notified user ${userId} of ${newEmails.length} new emails`);
|
this.logger.log(`Notified user ${userId} of ${newEmails.length} new emails`);
|
||||||
} else {
|
} else {
|
||||||
this.logger.debug(`No new emails found for user ${userId}`);
|
this.logger.debug(`No new emails found for user ${userId}`);
|
||||||
@@ -115,38 +106,4 @@ export class EmailPollingProcessor extends WorkerProcessor {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//=======================================================
|
|
||||||
private async getUnreadCounts(wildduckUserId: string, mailboxIds: UserMailboxIds) {
|
|
||||||
try {
|
|
||||||
const mailboxCounts: Record<string, number> = {};
|
|
||||||
let total = 0;
|
|
||||||
|
|
||||||
for (const [mailboxName, mailboxId] of Object.entries(mailboxIds)) {
|
|
||||||
if (mailboxId) {
|
|
||||||
try {
|
|
||||||
const response = await firstValueFrom(this.mailServerService.mailboxes.getMailbox(wildduckUserId, mailboxId));
|
|
||||||
|
|
||||||
if (response.success && response.unseen !== undefined) {
|
|
||||||
mailboxCounts[mailboxName] = response.unseen;
|
|
||||||
total += response.unseen;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.warn(`Error getting unread count for ${mailboxName} (${mailboxId}):`, error);
|
|
||||||
mailboxCounts[mailboxName] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
total,
|
|
||||||
mailboxCounts,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(`Error calculating unread counts for user ${wildduckUserId}:`, error);
|
|
||||||
return {
|
|
||||||
total: 0,
|
|
||||||
mailboxCounts: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,19 @@
|
|||||||
import { EntityManager } from "@mikro-orm/core";
|
|
||||||
import { Injectable, Logger } from "@nestjs/common";
|
import { Injectable, Logger } from "@nestjs/common";
|
||||||
import { firstValueFrom } from "rxjs";
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { MailboxResolverService } from "./mailbox-resolver.service";
|
import { MailboxResolverService } from "./mailbox-resolver.service";
|
||||||
import { WebSocketEvent } from "../../email/constants/email-events.constant";
|
import { WebSocketEvent } from "../../email/constants/email-events.constant";
|
||||||
import {
|
import {
|
||||||
|
IEmailDeletePayload,
|
||||||
IEmailPayload,
|
IEmailPayload,
|
||||||
IEmailSentNotificationParams,
|
|
||||||
IEmailSentPayload,
|
IEmailSentPayload,
|
||||||
IEmailStatusNotificationParams,
|
IEmailStatusPayload,
|
||||||
IMailboxUpdateNotificationParams,
|
|
||||||
IMailboxUpdatePayload,
|
IMailboxUpdatePayload,
|
||||||
IUnreadCountPayload,
|
IUnreadCountPayload,
|
||||||
} from "../../email/interfaces/email-events.interface";
|
} from "../../email/interfaces/email-events.interface";
|
||||||
import { IEmailStatusPayload } from "../../email/interfaces/email-events.interface";
|
|
||||||
import { IEmailDeletePayload } from "../../email/interfaces/email-events.interface";
|
|
||||||
import { EmailGateway } from "../../email-gateway/email.gateway";
|
import { EmailGateway } from "../../email-gateway/email.gateway";
|
||||||
import { MailServerService } from "../../mail-server/services/mail-server.service";
|
import { MailServerService } from "../../mail-server/services/mail-server.service";
|
||||||
import { MailboxEnum } from "../../mailbox/enums/mailbox.enum";
|
|
||||||
import { NotificationQueue } from "../../notifications/queue/notification.queue";
|
import { NotificationQueue } from "../../notifications/queue/notification.queue";
|
||||||
import { User } from "../../users/entities/user.entity";
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class EmailNotificationService {
|
export class EmailNotificationService {
|
||||||
@@ -30,298 +24,109 @@ export class EmailNotificationService {
|
|||||||
private readonly mailboxResolver: MailboxResolverService,
|
private readonly mailboxResolver: MailboxResolverService,
|
||||||
private readonly mailServerService: MailServerService,
|
private readonly mailServerService: MailServerService,
|
||||||
private readonly notificationQueue: NotificationQueue,
|
private readonly notificationQueue: NotificationQueue,
|
||||||
private readonly em: EntityManager,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
//************************************************************************ */
|
//************************************************************************ */
|
||||||
|
|
||||||
async notifyNewEmail(params: IEmailPayload) {
|
async notifyNewEmail(newEmailData: IEmailPayload) {
|
||||||
try {
|
try {
|
||||||
const em = this.em.fork();
|
const success = await this.emailGateway.notifyNewEmail(newEmailData.userId, newEmailData);
|
||||||
const user = await em.findOne(User, {
|
|
||||||
wildduckUserId: params.wildduckUserId,
|
await this.notificationQueue.addNewEmailNotification(newEmailData.userId, {
|
||||||
emailEnabled: true,
|
fromEmail: newEmailData.from.address,
|
||||||
deletedAt: null,
|
fromName: newEmailData.from.name || "Unknown Sender",
|
||||||
|
subject: newEmailData.subject,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user) {
|
await this.updateUnreadCount(newEmailData.wildduckUserId, newEmailData.userId);
|
||||||
this.logger.warn(`User not found with wildduckUserId: ${params.wildduckUserId}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create WebSocket payload
|
|
||||||
const emailPayload: IEmailPayload = {
|
|
||||||
wildduckUserId: params.wildduckUserId,
|
|
||||||
messageId: params.messageId,
|
|
||||||
userId: user.id,
|
|
||||||
date: params.date,
|
|
||||||
from: params.from,
|
|
||||||
to: params.to,
|
|
||||||
subject: params.subject,
|
|
||||||
hasAttachments: params.hasAttachments,
|
|
||||||
mailboxName: params.mailboxName,
|
|
||||||
isRead: false,
|
|
||||||
mailboxId: params.mailboxId,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Send WebSocket notification
|
|
||||||
const success = await this.emailGateway.notifyNewEmail(user.id, emailPayload);
|
|
||||||
|
|
||||||
// Send push notification
|
|
||||||
await this.notificationQueue.addNewEmailNotification(user.id, {
|
|
||||||
fromEmail: params.from.address,
|
|
||||||
fromName: params.from.name || "Unknown Sender",
|
|
||||||
subject: params.subject,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update unread count
|
|
||||||
await this.updateUnreadCount(user);
|
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
this.logger.log(`New email notification dispatched for user ${user.id}: ${params.subject}`);
|
this.logger.log(`New email notification dispatched for user ${newEmailData.userId}: ${newEmailData.subject}`);
|
||||||
} else {
|
} else {
|
||||||
this.logger.warn(`Failed to dispatch new email notification for user ${user.id}: user not connected`);
|
this.logger.warn(`Failed to dispatch new email notification for user ${newEmailData.userId}: user not connected`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(`Failed to notify new email for user ${params.wildduckUserId}:`, error);
|
this.logger.error(`Failed to notify new email for user ${newEmailData.wildduckUserId}:`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//************************************************************************ */
|
//************************************************************************ */
|
||||||
async notifyEmailSent(params: IEmailSentNotificationParams) {
|
async notifyEmailSent(emailSentData: IEmailSentPayload) {
|
||||||
try {
|
try {
|
||||||
const payload: IEmailSentPayload = {
|
await this.emailGateway.notifyEmailSent(emailSentData.userId, emailSentData);
|
||||||
userId: params.userId,
|
await this.updateUnreadCount(emailSentData.wildduckUserId, emailSentData.userId);
|
||||||
messageId: params.messageId,
|
|
||||||
mailboxId: params.mailboxId,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
from: params.from,
|
|
||||||
to: params.recipients,
|
|
||||||
subject: params.subject,
|
|
||||||
hasAttachments: false,
|
|
||||||
mailboxName: MailboxEnum.SENT,
|
|
||||||
isRead: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
const success = await this.emailGateway.notifyEmailSent(params.userId, payload);
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
this.logger.log(`Email sent notification dispatched for user ${params.userId}: ${params.messageId}`);
|
|
||||||
} else {
|
|
||||||
this.logger.warn(`Failed to dispatch email sent notification for user ${params.userId}: user not connected`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(`Failed to notify email sent for user ${params.userId}:`, error);
|
this.logger.error(`Failed to notify email sent for user ${emailSentData.userId}:`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//************************************************************************ */
|
//************************************************************************ */
|
||||||
async notifyEmailUnread(params: IEmailStatusNotificationParams) {
|
async notifyEmailUnread(emailUnreadData: IEmailStatusPayload) {
|
||||||
try {
|
try {
|
||||||
const em = this.em.fork();
|
await this.emailGateway.notifyEmailUnread(emailUnreadData.userId, emailUnreadData);
|
||||||
const user = await em.findOne(User, {
|
await this.updateUnreadCount(emailUnreadData.wildduckUserId, emailUnreadData.userId);
|
||||||
wildduckUserId: params.userId,
|
|
||||||
emailEnabled: true,
|
|
||||||
deletedAt: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
this.logger.warn(`User not found with wildduckUserId: ${params.userId}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload: IEmailStatusPayload = {
|
|
||||||
userId: user.id,
|
|
||||||
messageId: params.messageId,
|
|
||||||
mailboxId: params.mailboxId,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const success = await this.emailGateway.notifyEmailUnread(user.id, payload);
|
|
||||||
await this.updateUnreadCount(user);
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
this.logger.log(`Email unread notification dispatched for user ${user.id}: message ${params.messageId}`);
|
|
||||||
} else {
|
|
||||||
this.logger.warn(`Failed to dispatch email unread notification for user ${user.id}: user not connected`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(`Failed to notify email unread for user ${params.userId}:`, error);
|
this.logger.error(`Failed to notify email unread for user ${emailUnreadData.userId}:`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//************************************************************************ */
|
//************************************************************************ */
|
||||||
async notifyEmailRead(params: IEmailStatusNotificationParams) {
|
async notifyEmailRead(emailReadData: IEmailStatusPayload) {
|
||||||
try {
|
try {
|
||||||
const em = this.em.fork();
|
await this.emailGateway.notifyEmailRead(emailReadData.userId, emailReadData);
|
||||||
const user = await em.findOne(User, {
|
await this.updateUnreadCount(emailReadData.wildduckUserId, emailReadData.userId);
|
||||||
wildduckUserId: params.userId,
|
|
||||||
emailEnabled: true,
|
|
||||||
deletedAt: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
this.logger.warn(`User not found with wildduckUserId: ${params.userId}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload: IEmailStatusPayload = {
|
|
||||||
userId: user.id,
|
|
||||||
messageId: params.messageId,
|
|
||||||
mailboxId: params.mailboxId,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const success = await this.emailGateway.notifyEmailRead(user.id, payload);
|
|
||||||
await this.updateUnreadCount(user);
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
this.logger.log(`Email read notification dispatched for user ${user.id}: message ${params.messageId}`);
|
|
||||||
} else {
|
|
||||||
this.logger.warn(`Failed to dispatch email read notification for user ${user.id}: user not connected`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(`Failed to notify email read for user ${params.userId}:`, error);
|
this.logger.error(`Failed to notify email read for user ${emailReadData.userId}:`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//************************************************************************ */
|
//************************************************************************ */
|
||||||
async notifyEmailDeleted(params: IEmailStatusNotificationParams) {
|
async notifyEmailDeleted(emailDeletedData: IEmailDeletePayload) {
|
||||||
try {
|
try {
|
||||||
const em = this.em.fork();
|
await this.emailGateway.notifyEmailDeleted(emailDeletedData.userId, emailDeletedData);
|
||||||
const user = await em.findOne(User, {
|
await this.updateUnreadCount(emailDeletedData.wildduckUserId, emailDeletedData.userId);
|
||||||
wildduckUserId: params.userId,
|
|
||||||
emailEnabled: true,
|
|
||||||
deletedAt: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
this.logger.warn(`User not found with wildduckUserId: ${params.userId}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload: IEmailDeletePayload = {
|
|
||||||
userId: user.id,
|
|
||||||
messageId: params.messageId,
|
|
||||||
mailboxId: params.mailboxId,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const success = await this.emailGateway.notifyEmailDeleted(user.id, payload);
|
|
||||||
await this.updateUnreadCount(user);
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
this.logger.log(`Email deleted notification dispatched for user ${user.id}: message ${params.messageId}`);
|
|
||||||
} else {
|
|
||||||
this.logger.warn(`Failed to dispatch email deleted notification for user ${user.id}: user not connected`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(`Failed to notify email deleted for user ${params.userId}:`, error);
|
this.logger.error(`Failed to notify email deleted for user ${emailDeletedData.userId}:`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//************************************************************************ */
|
//************************************************************************ */
|
||||||
async notifyEmailFlagged(params: IEmailStatusNotificationParams) {
|
async notifyEmailFlagged(emailFlaggedData: IEmailStatusPayload) {
|
||||||
try {
|
try {
|
||||||
// Find the user to get the correct User entity ID for websocket notifications
|
await this.emailGateway.notifyEmailFlagged(emailFlaggedData.userId, emailFlaggedData);
|
||||||
const em = this.em.fork();
|
await this.updateUnreadCount(emailFlaggedData.wildduckUserId, emailFlaggedData.userId);
|
||||||
const user = await em.findOne(User, {
|
|
||||||
wildduckUserId: params.userId,
|
|
||||||
emailEnabled: true,
|
|
||||||
deletedAt: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
this.logger.warn(`User not found with wildduckUserId: ${params.userId}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload: IEmailStatusPayload = {
|
|
||||||
userId: user.id, // Use User entity ID for websocket
|
|
||||||
messageId: params.messageId,
|
|
||||||
mailboxId: params.mailboxId,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const success = await this.emailGateway.notifyEmailFlagged(user.id, payload);
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
this.logger.log(`Email flagged notification dispatched for user ${user.id}: message ${params.messageId}`);
|
|
||||||
} else {
|
|
||||||
this.logger.warn(`Failed to dispatch email flagged notification for user ${user.id}: user not connected`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(`Failed to notify email flagged for user ${params.userId}:`, error);
|
this.logger.error(`Failed to notify email flagged for user ${emailFlaggedData.userId}:`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//************************************************************************ */
|
//************************************************************************ */
|
||||||
async notifyEmailUnflagged(params: IEmailStatusNotificationParams) {
|
async notifyEmailUnflagged(emailUnflaggedData: IEmailStatusPayload) {
|
||||||
try {
|
try {
|
||||||
// Find the user to get the correct User entity ID for websocket notifications
|
await this.emailGateway.notifyEmailUnflagged(emailUnflaggedData.userId, emailUnflaggedData);
|
||||||
const em = this.em.fork();
|
await this.updateUnreadCount(emailUnflaggedData.wildduckUserId, emailUnflaggedData.userId);
|
||||||
const user = await em.findOne(User, {
|
|
||||||
wildduckUserId: params.userId,
|
|
||||||
emailEnabled: true,
|
|
||||||
deletedAt: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
this.logger.warn(`User not found with wildduckUserId: ${params.userId}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload: IEmailStatusPayload = {
|
|
||||||
userId: user.id, // Use User entity ID for websocket
|
|
||||||
messageId: params.messageId,
|
|
||||||
mailboxId: params.mailboxId,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const success = await this.emailGateway.notifyEmailUnflagged(user.id, payload);
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
this.logger.log(`Email unflagged notification dispatched for user ${user.id}: message ${params.messageId}`);
|
|
||||||
} else {
|
|
||||||
this.logger.warn(`Failed to dispatch email unflagged notification for user ${user.id}: user not connected`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(`Failed to notify email unflagged for user ${params.userId}:`, error);
|
this.logger.error(`Failed to notify email unflagged for user ${emailUnflaggedData.userId}:`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//************************************************************************ */
|
//************************************************************************ */
|
||||||
async notifyMailboxUpdate(params: IMailboxUpdateNotificationParams) {
|
async notifyMailboxUpdate(emailMailboxUpdateData: IMailboxUpdatePayload) {
|
||||||
try {
|
try {
|
||||||
const payload: IMailboxUpdatePayload = {
|
await this.emailGateway.notifyMailboxUpdate(emailMailboxUpdateData.userId, emailMailboxUpdateData);
|
||||||
userId: params.userId,
|
await this.updateUnreadCount(emailMailboxUpdateData.wildduckUserId, emailMailboxUpdateData.userId);
|
||||||
mailboxId: params.mailboxId,
|
|
||||||
mailboxName: params.mailboxName,
|
|
||||||
unreadCount: params.unreadCount,
|
|
||||||
totalCount: params.totalCount,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const success = await this.emailGateway.notifyMailboxUpdate(params.userId, payload);
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
this.logger.log(`Mailbox update notification dispatched for user ${params.userId}: ${params.mailboxName}`);
|
|
||||||
} else {
|
|
||||||
this.logger.warn(`Failed to dispatch mailbox update notification for user ${params.userId}: user not connected`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(`Failed to notify mailbox update for user ${params.userId}:`, error);
|
this.logger.error(`Failed to notify mailbox update for user ${emailMailboxUpdateData.userId}:`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//************************************************************************ */
|
//************************************************************************ */
|
||||||
private async updateUnreadCount(user: User) {
|
private async updateUnreadCount(wildduckUserId: string, userId: string) {
|
||||||
try {
|
try {
|
||||||
const inboxMailboxId = await this.mailboxResolver.getInboxMailboxId(user.wildduckUserId);
|
const inboxMailboxId = await this.mailboxResolver.getInboxMailboxId(wildduckUserId);
|
||||||
|
|
||||||
const { results } = await firstValueFrom(
|
const { results } = await firstValueFrom(
|
||||||
this.mailServerService.messages.listMessages(user.wildduckUserId, inboxMailboxId, {
|
this.mailServerService.messages.listMessages(wildduckUserId, inboxMailboxId, {
|
||||||
limit: 50,
|
limit: 50,
|
||||||
unseen: true,
|
unseen: true,
|
||||||
}),
|
}),
|
||||||
@@ -330,7 +135,7 @@ export class EmailNotificationService {
|
|||||||
const unreadCount = results.length;
|
const unreadCount = results.length;
|
||||||
|
|
||||||
const payload: IUnreadCountPayload = {
|
const payload: IUnreadCountPayload = {
|
||||||
userId: user.id, // Use the actual User entity ID for the notification payload
|
userId,
|
||||||
count: unreadCount,
|
count: unreadCount,
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
totalUnread: unreadCount,
|
totalUnread: unreadCount,
|
||||||
@@ -339,13 +144,13 @@ export class EmailNotificationService {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const success = await this.emailGateway.notifyUnreadCountUpdate(user.id, payload); // Use User entity ID here too
|
const success = await this.emailGateway.notifyUnreadCountUpdate(userId, payload);
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
this.logger.warn(`Failed to update unread count for user ${user.id}: user not connected`);
|
this.logger.warn(`Failed to update unread count for user ${userId}: user not connected`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(`Failed to update unread count for user ${user.wildduckUserId}:`, error);
|
this.logger.error(`Failed to update unread count for user ${wildduckUserId}:`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { SendEmailDto, UpdateDraftDto } from "./DTO/send-email.dto";
|
|||||||
import { EmailService } from "./services/email.service";
|
import { EmailService } from "./services/email.service";
|
||||||
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
|
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
|
||||||
import { UserDec } from "../../common/decorators/user.decorator";
|
import { UserDec } from "../../common/decorators/user.decorator";
|
||||||
|
import { User } from "../users/entities/user.entity";
|
||||||
|
|
||||||
@AuthGuards()
|
@AuthGuards()
|
||||||
@Controller("email")
|
@Controller("email")
|
||||||
@@ -41,8 +42,8 @@ export class EmailController {
|
|||||||
@ApiOperation({ summary: "Get a specific message" })
|
@ApiOperation({ summary: "Get a specific message" })
|
||||||
@ApiResponse({ status: 200, description: "Message details" })
|
@ApiResponse({ status: 200, description: "Message details" })
|
||||||
@ApiResponse({ status: 404, description: "Message not found" })
|
@ApiResponse({ status: 404, description: "Message not found" })
|
||||||
getMessage(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
getMessage(@UserDec() user: User, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
||||||
return this.emailService.getMessage(userId, params.messageId, query.mailbox);
|
return this.emailService.getMessage(user.wildduckUserId, user.id, params.messageId, query.mailbox);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete("messages/:messageId")
|
@Delete("messages/:messageId")
|
||||||
@@ -97,16 +98,16 @@ export class EmailController {
|
|||||||
@ApiOperation({ summary: "Mark a message as seen" })
|
@ApiOperation({ summary: "Mark a message as seen" })
|
||||||
@ApiResponse({ status: 200, description: "Message marked as seen successfully" })
|
@ApiResponse({ status: 200, description: "Message marked as seen successfully" })
|
||||||
@ApiResponse({ status: 404, description: "Message not found" })
|
@ApiResponse({ status: 404, description: "Message not found" })
|
||||||
markMessageAsSeen(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
markMessageAsSeen(@UserDec() user: User, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
||||||
return this.emailService.markMessageAsSeen(userId, Number(params.messageId), query.mailbox);
|
return this.emailService.markMessageAsSeen(user.wildduckUserId, user.id, Number(params.messageId), query.mailbox);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Patch("messages/:messageId/unseen")
|
@Patch("messages/:messageId/unseen")
|
||||||
@ApiOperation({ summary: "Mark a message as unseen" })
|
@ApiOperation({ summary: "Mark a message as unseen" })
|
||||||
@ApiResponse({ status: 200, description: "Message marked as unseen successfully" })
|
@ApiResponse({ status: 200, description: "Message marked as unseen successfully" })
|
||||||
@ApiResponse({ status: 404, description: "Message not found" })
|
@ApiResponse({ status: 404, description: "Message not found" })
|
||||||
markMessageAsUnseen(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
markMessageAsUnseen(@UserDec() user: User, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
||||||
return this.emailService.markMessageAsUnseen(userId, Number(params.messageId), query.mailbox);
|
return this.emailService.markMessageAsUnseen(user.wildduckUserId, user.id, Number(params.messageId), query.mailbox);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Patch("messages/mark-all-read")
|
@Patch("messages/mark-all-read")
|
||||||
@@ -115,24 +116,24 @@ export class EmailController {
|
|||||||
@ApiResponse({ status: 206, description: "Some messages marked as read, some failed" })
|
@ApiResponse({ status: 206, description: "Some messages marked as read, some failed" })
|
||||||
@ApiResponse({ status: 404, description: "Mailbox not found" })
|
@ApiResponse({ status: 404, description: "Mailbox not found" })
|
||||||
@ApiResponse({ status: 403, description: "Not authorized to mark messages as read" })
|
@ApiResponse({ status: 403, description: "Not authorized to mark messages as read" })
|
||||||
markAllMessagesAsRead(@UserDec("wildduckUserId") wildduckUserId: string, @UserDec("id") userId: string, @Query() query: MailboxIdQueryDto) {
|
markAllMessagesAsRead(@UserDec() user: User, @Query() query: MailboxIdQueryDto) {
|
||||||
return this.emailService.markAllMessagesAsRead(wildduckUserId, userId, query.mailbox);
|
return this.emailService.markAllMessagesAsRead(user.wildduckUserId, user.id, query.mailbox);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Patch("messages/empty-trash")
|
@Patch("messages/empty-trash")
|
||||||
@ApiOperation({ summary: "Empty trash" })
|
@ApiOperation({ summary: "Empty trash" })
|
||||||
@ApiResponse({ status: 200, description: "Trash emptied successfully" })
|
@ApiResponse({ status: 200, description: "Trash emptied successfully" })
|
||||||
@ApiResponse({ status: 404, description: "Trash not found" })
|
@ApiResponse({ status: 404, description: "Trash not found" })
|
||||||
emptyTrash(@UserDec("wildduckUserId") wildduckUserId: string, @UserDec("id") userId: string, @Query() query: MailboxIdQueryDto) {
|
emptyTrash(@UserDec() user: User, @Query() query: MailboxIdQueryDto) {
|
||||||
return this.emailService.emptyTrash(wildduckUserId, userId, query.mailbox);
|
return this.emailService.emptyTrash(user.wildduckUserId, user.id, query.mailbox);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Patch("messages/empty-spam")
|
@Patch("messages/empty-spam")
|
||||||
@ApiOperation({ summary: "Empty spam" })
|
@ApiOperation({ summary: "Empty spam" })
|
||||||
@ApiResponse({ status: 200, description: "Spam emptied successfully" })
|
@ApiResponse({ status: 200, description: "Spam emptied successfully" })
|
||||||
@ApiResponse({ status: 404, description: "Spam not found" })
|
@ApiResponse({ status: 404, description: "Spam not found" })
|
||||||
emptySpam(@UserDec("wildduckUserId") wildduckUserId: string, @UserDec("id") userId: string, @Query() query: MailboxIdQueryDto) {
|
emptySpam(@UserDec() user: User, @Query() query: MailboxIdQueryDto) {
|
||||||
return this.emailService.emptySpam(wildduckUserId, userId, query.mailbox);
|
return this.emailService.emptySpam(user.wildduckUserId, user.id, query.mailbox);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get("messages/inbox")
|
@Get("messages/inbox")
|
||||||
@@ -251,8 +252,8 @@ export class EmailController {
|
|||||||
@ApiResponse({ status: 200, description: "Message starred successfully" })
|
@ApiResponse({ status: 200, description: "Message starred successfully" })
|
||||||
@ApiResponse({ status: 404, description: "Message not found" })
|
@ApiResponse({ status: 404, description: "Message not found" })
|
||||||
@ApiResponse({ status: 403, description: "Not authorized to star message" })
|
@ApiResponse({ status: 403, description: "Not authorized to star message" })
|
||||||
starMessage(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
starMessage(@UserDec() user: User, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
||||||
return this.emailService.starMessage(userId, Number(params.messageId), query.mailbox);
|
return this.emailService.starMessage(user.wildduckUserId, user.id, Number(params.messageId), query.mailbox);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Patch("messages/:messageId/unfavorite")
|
@Patch("messages/:messageId/unfavorite")
|
||||||
@@ -260,8 +261,8 @@ export class EmailController {
|
|||||||
@ApiResponse({ status: 200, description: "Message unstarred successfully" })
|
@ApiResponse({ status: 200, description: "Message unstarred successfully" })
|
||||||
@ApiResponse({ status: 404, description: "Message not found" })
|
@ApiResponse({ status: 404, description: "Message not found" })
|
||||||
@ApiResponse({ status: 403, description: "Not authorized to unstar message" })
|
@ApiResponse({ status: 403, description: "Not authorized to unstar message" })
|
||||||
unstarMessage(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
unstarMessage(@UserDec() user: User, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
||||||
return this.emailService.unstarMessage(userId, Number(params.messageId), query.mailbox);
|
return this.emailService.unstarMessage(user.wildduckUserId, user.id, Number(params.messageId), query.mailbox);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Patch("messages/:messageId/toggle-favorite")
|
@Patch("messages/:messageId/toggle-favorite")
|
||||||
@@ -269,8 +270,8 @@ export class EmailController {
|
|||||||
@ApiResponse({ status: 200, description: "Message star status toggled successfully" })
|
@ApiResponse({ status: 200, description: "Message star status toggled successfully" })
|
||||||
@ApiResponse({ status: 404, description: "Message not found" })
|
@ApiResponse({ status: 404, description: "Message not found" })
|
||||||
@ApiResponse({ status: 403, description: "Not authorized to modify message" })
|
@ApiResponse({ status: 403, description: "Not authorized to modify message" })
|
||||||
toggleMessageStar(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
toggleMessageStar(@UserDec() user: User, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
||||||
return this.emailService.toggleMessageStar(userId, Number(params.messageId), query.mailbox);
|
return this.emailService.toggleMessageStar(user.wildduckUserId, user.id, Number(params.messageId), query.mailbox);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Patch("messages/:messageId/restore")
|
@Patch("messages/:messageId/restore")
|
||||||
@@ -309,7 +310,7 @@ export class EmailController {
|
|||||||
@ApiResponse({ status: 206, description: "Bulk action partially completed" })
|
@ApiResponse({ status: 206, description: "Bulk action partially completed" })
|
||||||
@ApiResponse({ status: 400, description: "Invalid bulk action data" })
|
@ApiResponse({ status: 400, description: "Invalid bulk action data" })
|
||||||
@ApiResponse({ status: 403, description: "Not authorized to perform bulk action" })
|
@ApiResponse({ status: 403, description: "Not authorized to perform bulk action" })
|
||||||
performBulkAction(@UserDec("wildduckUserId") userId: string, @Body() bulkActionDto: BulkActionDto, @Query() query: MailboxIdQueryDto) {
|
performBulkAction(@UserDec() user: User, @Body() bulkActionDto: BulkActionDto, @Query() query: MailboxIdQueryDto) {
|
||||||
return this.emailService.performBulkAction(userId, bulkActionDto, query.mailbox);
|
return this.emailService.performBulkAction(user.wildduckUserId, user.id, bulkActionDto, query.mailbox);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,46 +1,66 @@
|
|||||||
// Base interface with common properties
|
// ====================
|
||||||
|
// BASE INTERFACES
|
||||||
|
// ====================
|
||||||
|
|
||||||
export interface IBaseEventPayload {
|
export interface IBaseEventPayload {
|
||||||
userId: string;
|
userId: string;
|
||||||
timestamp: string;
|
timestamp: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Base interface for message-related events
|
|
||||||
export interface IBaseMessageEventPayload extends IBaseEventPayload {
|
export interface IBaseMessageEventPayload extends IBaseEventPayload {
|
||||||
messageId: number;
|
messageId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Base interface for mailbox-related events
|
|
||||||
export interface IBaseMailboxEventPayload extends IBaseEventPayload {
|
export interface IBaseMailboxEventPayload extends IBaseEventPayload {
|
||||||
mailboxId: string;
|
mailboxId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Base interface for message events that also involve mailboxes
|
|
||||||
export interface IBaseMessageMailboxEventPayload extends IBaseMessageEventPayload {
|
export interface IBaseMessageMailboxEventPayload extends IBaseMessageEventPayload {
|
||||||
mailboxId: string;
|
mailboxId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specific email event interfaces
|
// ====================
|
||||||
export interface IEmailPayload extends IBaseMessageMailboxEventPayload {
|
// COMMON EMAIL INTERFACES
|
||||||
wildduckUserId: string;
|
// ====================
|
||||||
userId: string;
|
|
||||||
messageId: number;
|
export interface IEmailContact {
|
||||||
date: string;
|
name?: string;
|
||||||
from: {
|
address: string;
|
||||||
name?: string;
|
}
|
||||||
address: string;
|
|
||||||
};
|
export interface IEmailMetadata {
|
||||||
to: Array<{
|
|
||||||
name?: string;
|
|
||||||
address: string;
|
|
||||||
}>;
|
|
||||||
subject: string;
|
subject: string;
|
||||||
preview?: string;
|
|
||||||
hasAttachments: boolean;
|
hasAttachments: boolean;
|
||||||
mailboxName: string;
|
mailboxName: string;
|
||||||
isRead: boolean;
|
isRead: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IEmailStatusPayload = IBaseMessageMailboxEventPayload;
|
export interface IEmailContent extends IEmailMetadata {
|
||||||
|
from: IEmailContact;
|
||||||
|
to: IEmailContact[];
|
||||||
|
preview?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================
|
||||||
|
// SPECIFIC EVENT PAYLOADS
|
||||||
|
// ====================
|
||||||
|
|
||||||
|
export interface IEmailPayload extends IBaseMessageMailboxEventPayload, IEmailContent {
|
||||||
|
wildduckUserId: string;
|
||||||
|
date: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IEmailSentPayload extends IBaseMessageMailboxEventPayload, IEmailContent {
|
||||||
|
wildduckUserId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IEmailStatusPayload extends IBaseMessageMailboxEventPayload {
|
||||||
|
wildduckUserId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IEmailDeletePayload extends IBaseMessageMailboxEventPayload {
|
||||||
|
wildduckUserId: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IEmailMovePayload extends IBaseMessageEventPayload {
|
export interface IEmailMovePayload extends IBaseMessageEventPayload {
|
||||||
fromMailboxId: string;
|
fromMailboxId: string;
|
||||||
@@ -49,27 +69,11 @@ export interface IEmailMovePayload extends IBaseMessageEventPayload {
|
|||||||
toMailboxName: string;
|
toMailboxName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IEmailDeletePayload = IBaseMessageMailboxEventPayload;
|
|
||||||
|
|
||||||
export interface IEmailSentPayload extends IBaseMessageMailboxEventPayload {
|
|
||||||
from: {
|
|
||||||
name?: string;
|
|
||||||
address: string;
|
|
||||||
};
|
|
||||||
to: Array<{
|
|
||||||
name?: string;
|
|
||||||
address: string;
|
|
||||||
}>;
|
|
||||||
subject: string;
|
|
||||||
hasAttachments: boolean;
|
|
||||||
mailboxName: string;
|
|
||||||
isRead: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IMailboxUpdatePayload extends IBaseMailboxEventPayload {
|
export interface IMailboxUpdatePayload extends IBaseMailboxEventPayload {
|
||||||
mailboxName: string;
|
mailboxName: string;
|
||||||
unreadCount: number;
|
unreadCount: number;
|
||||||
totalCount: number;
|
totalCount: number;
|
||||||
|
wildduckUserId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IUnreadCountPayload extends IBaseEventPayload {
|
export interface IUnreadCountPayload extends IBaseEventPayload {
|
||||||
@@ -78,6 +82,21 @@ export interface IUnreadCountPayload extends IBaseEventPayload {
|
|||||||
count: number;
|
count: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ====================
|
||||||
|
// NOTIFICATION PARAMETERS
|
||||||
|
// ====================
|
||||||
|
|
||||||
|
export interface INewEmailData {
|
||||||
|
userId: string;
|
||||||
|
messageId: number;
|
||||||
|
wildduckUserId: string;
|
||||||
|
mailboxId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================
|
||||||
|
// WEBSOCKET EVENTS
|
||||||
|
// ====================
|
||||||
|
|
||||||
export interface IEmailWebSocketEvents {
|
export interface IEmailWebSocketEvents {
|
||||||
// Server to Client events
|
// Server to Client events
|
||||||
new_email: IEmailPayload;
|
new_email: IEmailPayload;
|
||||||
@@ -100,33 +119,12 @@ export interface IEmailWebSocketEvents {
|
|||||||
pong: { timestamp: string };
|
pong: { timestamp: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface INewEmailData {
|
// ====================
|
||||||
userId: string;
|
// TYPE UNIONS FOR CONVENIENCE
|
||||||
messageId: number;
|
// ====================
|
||||||
wildduckUserId: string;
|
|
||||||
mailboxId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper interfaces for notification service parameters
|
export type EmailEventPayload = IEmailPayload | IEmailSentPayload | IEmailStatusPayload | IEmailDeletePayload | IEmailMovePayload;
|
||||||
export interface IEmailSentNotificationParams {
|
|
||||||
userId: string;
|
|
||||||
messageId: number;
|
|
||||||
mailboxId: string;
|
|
||||||
recipients: Array<{ name?: string; address: string }>;
|
|
||||||
subject: string;
|
|
||||||
from: { name?: string; address: string };
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IEmailStatusNotificationParams {
|
export type MailboxEventPayload = IMailboxUpdatePayload | IUnreadCountPayload;
|
||||||
userId: string;
|
|
||||||
messageId: number;
|
|
||||||
mailboxId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IMailboxUpdateNotificationParams {
|
export type AllEventPayloads = EmailEventPayload | MailboxEventPayload;
|
||||||
userId: string;
|
|
||||||
mailboxId: string;
|
|
||||||
mailboxName: string;
|
|
||||||
unreadCount: number;
|
|
||||||
totalCount: number;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ export class EmailBulkActionProcessor extends WorkerHost {
|
|||||||
|
|
||||||
for (const message of response.results) {
|
for (const message of response.results) {
|
||||||
try {
|
try {
|
||||||
await this.emailService.markMessageAsSeen(wildduckUserId, message.id, mailboxId);
|
await this.emailService.markMessageAsSeen(wildduckUserId, userId, message.id, mailboxId);
|
||||||
results.successful.push(message.id);
|
results.successful.push(message.id);
|
||||||
totalMarked++;
|
totalMarked++;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -109,7 +109,9 @@ export class EmailBulkActionProcessor extends WorkerHost {
|
|||||||
results.totalMarked = totalMarked;
|
results.totalMarked = totalMarked;
|
||||||
|
|
||||||
await this.emailNotificationService.notifyEmailRead({
|
await this.emailNotificationService.notifyEmailRead({
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
userId,
|
userId,
|
||||||
|
wildduckUserId,
|
||||||
messageId: results.successful[0],
|
messageId: results.successful[0],
|
||||||
mailboxId,
|
mailboxId,
|
||||||
});
|
});
|
||||||
@@ -143,7 +145,9 @@ export class EmailBulkActionProcessor extends WorkerHost {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await this.emailNotificationService.notifyEmailDeleted({
|
await this.emailNotificationService.notifyEmailDeleted({
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
userId,
|
userId,
|
||||||
|
wildduckUserId,
|
||||||
messageId: randomInt(1, 500),
|
messageId: randomInt(1, 500),
|
||||||
mailboxId,
|
mailboxId,
|
||||||
});
|
});
|
||||||
@@ -172,7 +176,9 @@ export class EmailBulkActionProcessor extends WorkerHost {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await this.emailNotificationService.notifyEmailDeleted({
|
await this.emailNotificationService.notifyEmailDeleted({
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
userId,
|
userId,
|
||||||
|
wildduckUserId,
|
||||||
messageId: randomInt(1, 500),
|
messageId: randomInt(1, 500),
|
||||||
mailboxId,
|
mailboxId,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -79,12 +79,17 @@ export class EmailService {
|
|||||||
const sentMailboxId = await this.mailboxResolverService.getSentMailboxId(wildduckUserId);
|
const sentMailboxId = await this.mailboxResolverService.getSentMailboxId(wildduckUserId);
|
||||||
|
|
||||||
await this.emailNotificationService.notifyEmailSent({
|
await this.emailNotificationService.notifyEmailSent({
|
||||||
userId: wildduckUserId,
|
wildduckUserId,
|
||||||
|
userId,
|
||||||
messageId: parseInt(response.message.id),
|
messageId: parseInt(response.message.id),
|
||||||
mailboxId: sentMailboxId,
|
mailboxId: sentMailboxId,
|
||||||
recipients: sendEmailDto.to,
|
to: sendEmailDto.to,
|
||||||
subject: sendEmailDto.subject,
|
subject: sendEmailDto.subject,
|
||||||
from: sendEmailDto.from,
|
from: sendEmailDto.from,
|
||||||
|
hasAttachments: false,
|
||||||
|
mailboxName: "Sent",
|
||||||
|
isRead: true,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -217,8 +222,8 @@ export class EmailService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//########################################################
|
//########################################################
|
||||||
async getMessage(wildduckUserId: string, messageId: number, mailboxId: string) {
|
async getMessage(wildduckUserId: string, userId: string, messageId: number, mailboxId: string) {
|
||||||
await this.markMessageAsSeen(wildduckUserId, messageId, mailboxId);
|
await this.markMessageAsSeen(wildduckUserId, userId, messageId, mailboxId);
|
||||||
const message = await firstValueFrom(this.mailServerService.messages.getMessage(wildduckUserId, mailboxId, messageId));
|
const message = await firstValueFrom(this.mailServerService.messages.getMessage(wildduckUserId, mailboxId, messageId));
|
||||||
|
|
||||||
const mailboxName = await this.mailboxResolverService.getMailboxName(wildduckUserId, mailboxId);
|
const mailboxName = await this.mailboxResolverService.getMailboxName(wildduckUserId, mailboxId);
|
||||||
@@ -253,13 +258,15 @@ export class EmailService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//==============================================
|
//==============================================
|
||||||
async markMessageAsSeen(wildduckUserId: string, messageId: number, mailboxId: string) {
|
async markMessageAsSeen(wildduckUserId: string, userId: string, messageId: number, mailboxId: string) {
|
||||||
const { updated } = await firstValueFrom(this.mailServerService.messages.updateMessage(wildduckUserId, mailboxId, messageId, { seen: true }));
|
const { updated } = await firstValueFrom(this.mailServerService.messages.updateMessage(wildduckUserId, mailboxId, messageId, { seen: true }));
|
||||||
|
|
||||||
await this.emailNotificationService.notifyEmailRead({
|
await this.emailNotificationService.notifyEmailRead({
|
||||||
userId: wildduckUserId,
|
wildduckUserId,
|
||||||
|
userId,
|
||||||
messageId,
|
messageId,
|
||||||
mailboxId,
|
mailboxId,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -270,13 +277,15 @@ export class EmailService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//==============================================
|
//==============================================
|
||||||
async markMessageAsUnseen(wildduckUserId: string, messageId: number, mailboxId: string) {
|
async markMessageAsUnseen(wildduckUserId: string, userId: string, messageId: number, mailboxId: string) {
|
||||||
const { updated } = await firstValueFrom(this.mailServerService.messages.updateMessage(wildduckUserId, mailboxId, messageId, { seen: false }));
|
const { updated } = await firstValueFrom(this.mailServerService.messages.updateMessage(wildduckUserId, mailboxId, messageId, { seen: false }));
|
||||||
|
|
||||||
await this.emailNotificationService.notifyEmailUnread({
|
await this.emailNotificationService.notifyEmailUnread({
|
||||||
userId: wildduckUserId,
|
wildduckUserId,
|
||||||
|
userId,
|
||||||
messageId,
|
messageId,
|
||||||
mailboxId,
|
mailboxId,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
@@ -608,13 +617,15 @@ export class EmailService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//==============================================
|
//==============================================
|
||||||
async starMessage(wildduckUserId: string, messageId: number, mailboxId: string) {
|
async starMessage(wildduckUserId: string, userId: string, messageId: number, mailboxId: string) {
|
||||||
const result = await firstValueFrom(this.mailServerService.messages.updateMessage(wildduckUserId, mailboxId, messageId, { flagged: true }));
|
const result = await firstValueFrom(this.mailServerService.messages.updateMessage(wildduckUserId, mailboxId, messageId, { flagged: true }));
|
||||||
|
|
||||||
await this.emailNotificationService.notifyEmailFlagged({
|
await this.emailNotificationService.notifyEmailFlagged({
|
||||||
userId: wildduckUserId,
|
userId,
|
||||||
|
wildduckUserId,
|
||||||
messageId,
|
messageId,
|
||||||
mailboxId,
|
mailboxId,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -625,7 +636,7 @@ export class EmailService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//==============================================
|
//==============================================
|
||||||
async unstarMessage(wildduckUserId: string, messageId: number, mailboxId: string) {
|
async unstarMessage(wildduckUserId: string, userId: string, messageId: number, mailboxId: string) {
|
||||||
const result = await firstValueFrom(
|
const result = await firstValueFrom(
|
||||||
this.mailServerService.messages.updateMessage(wildduckUserId, mailboxId, messageId, {
|
this.mailServerService.messages.updateMessage(wildduckUserId, mailboxId, messageId, {
|
||||||
flagged: false,
|
flagged: false,
|
||||||
@@ -633,9 +644,11 @@ export class EmailService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await this.emailNotificationService.notifyEmailUnflagged({
|
await this.emailNotificationService.notifyEmailUnflagged({
|
||||||
userId: wildduckUserId,
|
userId,
|
||||||
|
wildduckUserId,
|
||||||
messageId,
|
messageId,
|
||||||
mailboxId,
|
mailboxId,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -646,13 +659,13 @@ export class EmailService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//==============================================
|
//==============================================
|
||||||
async toggleMessageStar(wildduckUserId: string, messageId: number, mailboxId: string) {
|
async toggleMessageStar(wildduckUserId: string, userId: string, messageId: number, mailboxId: string) {
|
||||||
const message = await firstValueFrom(this.mailServerService.messages.getMessage(wildduckUserId, mailboxId, messageId));
|
const message = await firstValueFrom(this.mailServerService.messages.getMessage(wildduckUserId, mailboxId, messageId));
|
||||||
|
|
||||||
if (message.flagged) {
|
if (message.flagged) {
|
||||||
return this.unstarMessage(wildduckUserId, messageId, mailboxId);
|
return this.unstarMessage(wildduckUserId, userId, messageId, mailboxId);
|
||||||
} else {
|
} else {
|
||||||
return this.starMessage(wildduckUserId, messageId, mailboxId);
|
return this.starMessage(wildduckUserId, userId, messageId, mailboxId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -726,7 +739,7 @@ export class EmailService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//==============================================
|
//==============================================
|
||||||
async performBulkAction(wildduckUserId: string, bulkActionDto: BulkActionDto, mailboxId: string) {
|
async performBulkAction(wildduckUserId: string, userId: string, bulkActionDto: BulkActionDto, mailboxId: string) {
|
||||||
const results = {
|
const results = {
|
||||||
successful: [] as number[],
|
successful: [] as number[],
|
||||||
failed: [] as { messageId: number; error: string }[],
|
failed: [] as { messageId: number; error: string }[],
|
||||||
@@ -736,7 +749,7 @@ export class EmailService {
|
|||||||
try {
|
try {
|
||||||
switch (bulkActionDto.action) {
|
switch (bulkActionDto.action) {
|
||||||
case BulkActionType.SEEN:
|
case BulkActionType.SEEN:
|
||||||
await this.markMessageAsSeen(wildduckUserId, messageId, mailboxId);
|
await this.markMessageAsSeen(wildduckUserId, userId, messageId, mailboxId);
|
||||||
break;
|
break;
|
||||||
case BulkActionType.DELETE:
|
case BulkActionType.DELETE:
|
||||||
await this.deleteMessage(wildduckUserId, messageId, mailboxId);
|
await this.deleteMessage(wildduckUserId, messageId, mailboxId);
|
||||||
@@ -748,10 +761,10 @@ export class EmailService {
|
|||||||
await this.moveMessageToInboxFromArchive(wildduckUserId, messageId, mailboxId);
|
await this.moveMessageToInboxFromArchive(wildduckUserId, messageId, mailboxId);
|
||||||
break;
|
break;
|
||||||
case BulkActionType.FAVORITE:
|
case BulkActionType.FAVORITE:
|
||||||
await this.starMessage(wildduckUserId, messageId, mailboxId);
|
await this.starMessage(wildduckUserId, userId, messageId, mailboxId);
|
||||||
break;
|
break;
|
||||||
case BulkActionType.UNFAVORITE:
|
case BulkActionType.UNFAVORITE:
|
||||||
await this.unstarMessage(wildduckUserId, messageId, mailboxId);
|
await this.unstarMessage(wildduckUserId, userId, messageId, mailboxId);
|
||||||
break;
|
break;
|
||||||
case BulkActionType.JUNK:
|
case BulkActionType.JUNK:
|
||||||
await this.moveMessageToJunk(wildduckUserId, messageId, mailboxId);
|
await this.moveMessageToJunk(wildduckUserId, messageId, mailboxId);
|
||||||
|
|||||||
@@ -88,6 +88,24 @@ export class NajvaPushService {
|
|||||||
|
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// Handle gateway/proxy timeout errors - notification likely sent successfully
|
||||||
|
if (error instanceof AxiosError && [502, 503, 504].includes(error.response?.status || 0)) {
|
||||||
|
this.logger.warn(
|
||||||
|
`Gateway/proxy error (${error.response?.status}) occurred, but notification likely sent successfully to ${validTokens.length} tokens`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Return a mock successful response for gateway errors since notification is likely delivered
|
||||||
|
return {
|
||||||
|
Entries: {
|
||||||
|
request_id: `gateway_error_${Date.now()}`,
|
||||||
|
tokens: validTokens.map((token) => ({
|
||||||
|
token,
|
||||||
|
status: "Sent" as const,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
this.logger.error("Failed to send push notification:", error);
|
this.logger.error("Failed to send push notification:", error);
|
||||||
|
|
||||||
if (error instanceof AxiosError) {
|
if (error instanceof AxiosError) {
|
||||||
@@ -111,6 +129,14 @@ export class NajvaPushService {
|
|||||||
const tokenResult = result.Entries.tokens.find((t) => t.token === userToken);
|
const tokenResult = result.Entries.tokens.find((t) => t.token === userToken);
|
||||||
return tokenResult?.status === "Sent";
|
return tokenResult?.status === "Sent";
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// Handle gateway/proxy timeout errors - notification likely sent successfully
|
||||||
|
if (error instanceof AxiosError && [502, 503, 504].includes(error.response?.status || 0)) {
|
||||||
|
this.logger.warn(
|
||||||
|
`Gateway/proxy error (${error.response?.status}) occurred for user token ${userToken}, but notification likely sent successfully`,
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
this.logger.error(`Failed to send notification to user token ${userToken}:`, error);
|
this.logger.error(`Failed to send notification to user token ${userToken}:`, error);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -131,6 +157,14 @@ export class NajvaPushService {
|
|||||||
|
|
||||||
return { success: successCount, failed: failedCount, invalidTokens };
|
return { success: successCount, failed: failedCount, invalidTokens };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// Handle gateway/proxy timeout errors - notification likely sent successfully
|
||||||
|
if (error instanceof AxiosError && [502, 503, 504].includes(error.response?.status || 0)) {
|
||||||
|
this.logger.warn(
|
||||||
|
`Gateway/proxy error (${error.response?.status}) occurred for ${userTokens.length} tokens, but notifications likely sent successfully`,
|
||||||
|
);
|
||||||
|
return { success: userTokens.length, failed: 0, invalidTokens: [] };
|
||||||
|
}
|
||||||
|
|
||||||
this.logger.error(`Failed to send notifications to multiple users:`, error);
|
this.logger.error(`Failed to send notifications to multiple users:`, error);
|
||||||
return { success: 0, failed: userTokens.length };
|
return { success: 0, failed: userTokens.length };
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user