feat: change the email structure into seprate module

This commit is contained in:
mahyargdz
2025-07-26 10:11:17 +03:30
parent 7b7167ab7d
commit 61e3be9f4a
15 changed files with 1226 additions and 71 deletions
@@ -0,0 +1,339 @@
import { EntityManager } from "@mikro-orm/core";
import { Injectable, Logger } from "@nestjs/common";
import { firstValueFrom } from "rxjs";
import { MailboxResolverService } from "./mailbox-resolver.service";
import { WebSocketEvent } from "../../email/constants/email-events.constant";
import {
EmailDeletePayload,
EmailSentNotificationParams,
EmailSentPayload,
EmailStatusNotificationParams,
EmailStatusPayload,
MailboxUpdateNotificationParams,
MailboxUpdatePayload,
UnreadCountPayload,
} from "../../email/interfaces/email-events.interface";
import { EmailGateway } from "../../email-gateway/email.gateway";
import { MailServerService } from "../../mail-server/services/mail-server.service";
import { MailboxEnum } from "../../mailbox/enums/mailbox.enum";
import { User } from "../../users/entities/user.entity";
@Injectable()
export class EmailNotificationService {
private readonly logger = new Logger(EmailNotificationService.name);
constructor(
private readonly emailGateway: EmailGateway,
private readonly mailboxResolver: MailboxResolverService,
private readonly mailServerService: MailServerService,
private readonly em: EntityManager,
) {}
//************************************************************************ */
async notifyEmailSent(params: EmailSentNotificationParams) {
try {
const payload: EmailSentPayload = {
userId: params.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) {
this.logger.error(`Failed to notify email sent for user ${params.userId}:`, error);
}
}
//************************************************************************ */
async notifyEmailUnread(params: EmailStatusNotificationParams) {
try {
const em = this.em.fork();
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: EmailStatusPayload = {
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(params.userId);
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) {
this.logger.error(`Failed to notify email unread for user ${params.userId}:`, error);
}
}
//************************************************************************ */
async notifyEmailRead(params: EmailStatusNotificationParams) {
try {
// Find the user to get the correct User entity ID for websocket notifications
const em = this.em.fork();
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: EmailStatusPayload = {
userId: user.id, // Use User entity ID for websocket
messageId: params.messageId,
mailboxId: params.mailboxId,
timestamp: new Date().toISOString(),
};
const success = await this.emailGateway.notifyEmailRead(user.id, payload);
await this.updateUnreadCount(params.userId); // Pass wildduckUserId to updateUnreadCount
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) {
this.logger.error(`Failed to notify email read for user ${params.userId}:`, error);
}
}
//************************************************************************ */
async notifyEmailDeleted(params: EmailStatusNotificationParams) {
try {
// Find the user to get the correct User entity ID for websocket notifications
const em = this.em.fork();
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: EmailDeletePayload = {
userId: user.id, // Use User entity ID for websocket
messageId: params.messageId,
mailboxId: params.mailboxId,
timestamp: new Date().toISOString(),
};
const success = await this.emailGateway.notifyEmailDeleted(user.id, payload);
await this.updateUnreadCount(params.userId); // Pass wildduckUserId to updateUnreadCount
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) {
this.logger.error(`Failed to notify email deleted for user ${params.userId}:`, error);
}
}
//************************************************************************ */
async notifyEmailFlagged(params: EmailStatusNotificationParams) {
try {
// Find the user to get the correct User entity ID for websocket notifications
const em = this.em.fork();
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: EmailStatusPayload = {
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) {
this.logger.error(`Failed to notify email flagged for user ${params.userId}:`, error);
}
}
//************************************************************************ */
async notifyEmailUnflagged(params: EmailStatusNotificationParams) {
try {
// Find the user to get the correct User entity ID for websocket notifications
const em = this.em.fork();
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: EmailStatusPayload = {
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) {
this.logger.error(`Failed to notify email unflagged for user ${params.userId}:`, error);
}
}
//************************************************************************ */
async notifyMailboxUpdate(params: MailboxUpdateNotificationParams) {
try {
const payload: MailboxUpdatePayload = {
userId: params.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) {
this.logger.error(`Failed to notify mailbox update for user ${params.userId}:`, error);
}
}
//************************************************************************ */
private async updateUnreadCount(userId: string) {
try {
const em = this.em.fork();
const user = await em.findOne(User, {
wildduckUserId: userId,
emailEnabled: true,
deletedAt: null,
});
if (!user || !user.wildduckUserId) {
return;
}
const inboxMailboxId = await this.mailboxResolver.getInboxMailboxId(user.wildduckUserId);
const { results } = await firstValueFrom(
this.mailServerService.messages.listMessages(user.wildduckUserId, inboxMailboxId, {
limit: 50,
unseen: true,
}),
);
const unreadCount = results.length;
const payload: UnreadCountPayload = {
userId: user.id, // Use the actual User entity ID for the notification payload
count: unreadCount,
timestamp: new Date().toISOString(),
totalUnread: unreadCount,
mailboxCounts: {
[inboxMailboxId]: unreadCount,
},
};
const success = await this.emailGateway.notifyUnreadCountUpdate(user.id, payload); // Use User entity ID here too
if (!success) {
this.logger.warn(`Failed to update unread count for user ${user.id}: user not connected`);
}
} catch (error) {
this.logger.error(`Failed to update unread count for user ${userId}:`, error);
}
}
//************************************************************************ */
async broadcastSystemNotification(event: WebSocketEvent, data: Record<string, unknown>) {
try {
this.emailGateway.broadcastSystemMessage(event, {
...data,
timestamp: new Date().toISOString(),
});
this.logger.log(`System notification broadcasted: ${event}`);
} catch (error) {
this.logger.error(`Failed to broadcast system notification:`, error);
}
}
//************************************************************************ */
async getGatewayStats() {
try {
const connectedUsers = await this.emailGateway.getConnectedUsersCount();
return {
connectedUsers,
timestamp: new Date().toISOString(),
};
} catch (error) {
this.logger.error("Failed to get gateway statistics:", error);
return {
connectedUsers: 0,
timestamp: new Date().toISOString(),
};
}
}
}