159 lines
5.7 KiB
TypeScript
159 lines
5.7 KiB
TypeScript
import { Injectable, Logger } from "@nestjs/common";
|
|
import { firstValueFrom } from "rxjs";
|
|
|
|
import { MailServerService } from "../../mail-server/services/mail-server.service";
|
|
import { EmailGateway } from "../email.gateway";
|
|
import { MailboxResolverService } from "./mailbox-resolver.service";
|
|
import { EmailNotificationPayload } from "../interfaces/email-events.interface";
|
|
|
|
@Injectable()
|
|
export class EmailNotificationService {
|
|
private readonly logger = new Logger(EmailNotificationService.name);
|
|
|
|
constructor(
|
|
private readonly emailGateway: EmailGateway,
|
|
private readonly mailServerService: MailServerService,
|
|
private readonly mailboxResolverService: MailboxResolverService,
|
|
) {}
|
|
|
|
/**
|
|
* Handle new incoming message and emit WebSocket notification
|
|
* This method can be called by webhook handlers, polling services, etc.
|
|
*/
|
|
async handleNewMessage(userId: string, messageId: number, mailboxId?: string) {
|
|
try {
|
|
this.logger.log(`Processing new message notification for user ${userId}, message ${messageId}`);
|
|
|
|
// Get the mailbox ID if not provided
|
|
const targetMailboxId = mailboxId || (await this.mailboxResolverService.getInboxMailboxId(userId));
|
|
|
|
// Fetch the message details
|
|
const message = await firstValueFrom(this.mailServerService.messages.getMessage(userId, targetMailboxId, messageId));
|
|
|
|
// Determine mailbox name
|
|
const mailboxName = await this.getMailboxName(userId, targetMailboxId);
|
|
|
|
// Create notification payload
|
|
const notificationPayload: EmailNotificationPayload = {
|
|
messageId,
|
|
userId,
|
|
from: {
|
|
name: message.from?.name || message.from?.address?.split("@")[0] || "Unknown",
|
|
address: message.from?.address || "unknown@example.com",
|
|
},
|
|
to:
|
|
message.to?.map((recipient) => ({
|
|
name: recipient.name || recipient.address?.split("@")[0] || "Unknown",
|
|
address: recipient.address || "unknown@example.com",
|
|
})) || [],
|
|
subject: message.subject || "No Subject",
|
|
preview: this.extractPreview(
|
|
typeof message.text === "string" ? message.text : Array.isArray(message.html) ? message.html.join("") : message.html || "",
|
|
),
|
|
hasAttachments: Array.isArray(message.attachments) && message.attachments.length > 0,
|
|
received: message.idate || new Date().toISOString(),
|
|
mailboxId: targetMailboxId,
|
|
mailboxName,
|
|
isRead: message.seen || false,
|
|
isFlagged: message.flagged || false,
|
|
size: message.size || 0,
|
|
};
|
|
|
|
// Emit WebSocket notification
|
|
await this.emailGateway.notifyNewMessage(notificationPayload);
|
|
|
|
this.logger.log(`New message notification sent for user ${userId}, message ${messageId}`);
|
|
|
|
return notificationPayload;
|
|
} catch (error) {
|
|
this.logger.error(
|
|
`Failed to process new message notification for user ${userId}, message ${messageId}: ${
|
|
error instanceof Error ? error.message : "Unknown error"
|
|
}`,
|
|
);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle bulk new messages (useful for initial sync or batch processing)
|
|
*/
|
|
async handleBulkNewMessages(userId: string, messageIds: number[], mailboxId?: string) {
|
|
this.logger.log(`Processing bulk new message notifications for user ${userId}, ${messageIds.length} messages`);
|
|
|
|
const results = [];
|
|
for (const messageId of messageIds) {
|
|
try {
|
|
const result = await this.handleNewMessage(userId, messageId, mailboxId);
|
|
results.push(result);
|
|
} catch (error) {
|
|
this.logger.error(`Failed to process message ${messageId} in bulk notification: ${error}`);
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
/**
|
|
* Simulate new message arrival (for testing purposes)
|
|
*/
|
|
async simulateNewMessage(userId: string, messageData: Partial<EmailNotificationPayload>) {
|
|
this.logger.log(`Simulating new message for user ${userId}`);
|
|
|
|
const simulatedPayload: EmailNotificationPayload = {
|
|
messageId: Math.floor(Math.random() * 10000),
|
|
userId,
|
|
from: {
|
|
name: "Test Sender",
|
|
address: "test@example.com",
|
|
},
|
|
to: [
|
|
{
|
|
name: "Test Recipient",
|
|
address: "recipient@example.com",
|
|
},
|
|
],
|
|
subject: "Test Message",
|
|
preview: "This is a test message for WebSocket notification",
|
|
hasAttachments: false,
|
|
received: new Date().toISOString(),
|
|
mailboxId: "inbox",
|
|
mailboxName: "Inbox",
|
|
isRead: false,
|
|
isFlagged: false,
|
|
size: 1024,
|
|
...messageData,
|
|
};
|
|
|
|
await this.emailGateway.notifyNewMessage(simulatedPayload);
|
|
|
|
return simulatedPayload;
|
|
}
|
|
|
|
private async getMailboxName(userId: string, mailboxId: string): Promise<string> {
|
|
try {
|
|
const mailboxIds = await this.mailboxResolverService.getUserMailboxIds(userId);
|
|
|
|
// Check which mailbox this ID corresponds to
|
|
if (mailboxIds.inbox === mailboxId) return "Inbox";
|
|
if (mailboxIds.sent === mailboxId) return "Sent";
|
|
if (mailboxIds.drafts === mailboxId) return "Drafts";
|
|
if (mailboxIds.trash === mailboxId) return "Trash";
|
|
if (mailboxIds.junk === mailboxId) return "Junk";
|
|
if (mailboxIds.archive === mailboxId) return "Archive";
|
|
if (mailboxIds.favorite === mailboxId) return "Favorite";
|
|
|
|
return "Unknown";
|
|
} catch (error) {
|
|
this.logger.error(`Failed to get mailbox name for ${mailboxId}: ${error}`);
|
|
return "Unknown";
|
|
}
|
|
}
|
|
|
|
private extractPreview(content: string): string {
|
|
// Remove HTML tags and get first 150 characters
|
|
const textContent = content.replace(/<[^>]*>/g, "").trim();
|
|
return textContent.length > 150 ? textContent.substring(0, 150) + "..." : textContent;
|
|
}
|
|
}
|