340 lines
12 KiB
TypeScript
340 lines
12 KiB
TypeScript
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(),
|
|
};
|
|
}
|
|
}
|
|
}
|