diff --git a/src/common/enums/message.enum.ts b/src/common/enums/message.enum.ts index 6492610..7455de2 100755 --- a/src/common/enums/message.enum.ts +++ b/src/common/enums/message.enum.ts @@ -203,6 +203,7 @@ export const enum EmailMessage { MESSAGE_MOVED_TO_TRASH_SUCCESSFULLY = "پیام با موفقیت به سطل زباله منتقل شد", MESSAGE_MOVED_TO_INBOX_FROM_ARCHIVE_SUCCESSFULLY = "پیام با موفقیت از آرشیو به صندوق ورودی منتقل شد", MESSAGE_MOVED_TO_INBOX_FROM_FAVORITE_SUCCESSFULLY = "پیام با موفقیت از علاقه مندی ها به صندوق ورودی منتقل شد", + MESSAGE_NOT_FOUND = "پیام یافت نشد", } export const enum WebSocketMessage { diff --git a/src/modules/email/email.controller.ts b/src/modules/email/email.controller.ts index f7a1681..dfbb2cc 100644 --- a/src/modules/email/email.controller.ts +++ b/src/modules/email/email.controller.ts @@ -31,7 +31,7 @@ export class EmailController { return this.emailService.getEmailStatus(params.queueId); } - @Get("messages/inbox/:messageId") + @Get("messages/:messageId") @ApiOperation({ summary: "Get a specific message" }) @ApiResponse({ status: 200, description: "Message details" }) @ApiResponse({ status: 404, description: "Message not found" }) @@ -39,7 +39,7 @@ export class EmailController { return this.emailService.getMessage(userId, params.messageId); } - @Delete("messages/inbox/:messageId") + @Delete("messages/:messageId") @ApiOperation({ summary: "Delete a message" }) @ApiResponse({ status: 200, description: "Message deleted successfully" }) @ApiResponse({ status: 404, description: "Message not found" }) diff --git a/src/modules/email/services/email.service.ts b/src/modules/email/services/email.service.ts index b0f8fae..6dfae53 100644 --- a/src/modules/email/services/email.service.ts +++ b/src/modules/email/services/email.service.ts @@ -1,9 +1,10 @@ -import { Injectable, Logger } from "@nestjs/common"; +import { BadRequestException, Injectable, Logger } from "@nestjs/common"; import { firstValueFrom } from "rxjs"; import { MailboxResolverService } from "./mailbox-resolver.service"; import { EmailMessage } from "../../../common/enums/message.enum"; import { MailServerService } from "../../mail-server/services/mail-server.service"; +import { MailboxEnum } from "../../mailbox/enums/mailbox.enum"; import { MessageListQueryDto, SearchMessagesQueryDto } from "../DTO/email-query.dto"; import { SendEmailDto, UpdateDraftDto } from "../DTO/send-email.dto"; import { EmailGateway } from "../email.gateway"; @@ -111,13 +112,16 @@ export class EmailService { //######################################################## async getMessage(userId: string, messageId: number) { try { - const inboxMailboxId = await this.mailboxResolverService.getInboxMailboxId(userId); - this.logger.log(`Getting message ${messageId} from inbox ${inboxMailboxId} for user: ${userId}`); + const messageLocation = await this.findMessageMailbox(userId, messageId); + + if (!messageLocation) throw new BadRequestException(EmailMessage.MESSAGE_NOT_FOUND); + + this.logger.log(`Getting message ${messageId} from ${messageLocation.mailboxName} mailbox for user: ${userId}`); await this.markMessageAsSeen(userId, messageId); - const message = await firstValueFrom(this.mailServerService.messages.getMessage(userId, inboxMailboxId, messageId)); + const message = await firstValueFrom(this.mailServerService.messages.getMessage(userId, messageLocation.mailboxId, messageId)); - this.logger.log(`Retrieved message ${messageId} for user: ${userId}`); + this.logger.log(`Retrieved message ${messageId} from ${messageLocation.mailboxName} for user: ${userId}`); return { message, @@ -128,22 +132,65 @@ export class EmailService { } } + //######################################################## + + private async findMessageMailbox(userId: string, messageId: number): Promise<{ mailboxId: string; mailboxName: string } | null> { + try { + const mailboxIds = await this.mailboxResolverService.getUserMailboxIds(userId); + + const mailboxesToSearch = [ + { id: mailboxIds.inbox, name: MailboxEnum.INBOX }, + { id: mailboxIds.sent, name: MailboxEnum.SENT }, + { id: mailboxIds.drafts, name: MailboxEnum.DRAFTS }, + { id: mailboxIds.trash, name: MailboxEnum.TRASH }, + { id: mailboxIds.archive, name: MailboxEnum.ARCHIVE }, + { id: mailboxIds.favorite, name: MailboxEnum.FAVORITE }, + { id: mailboxIds.junk, name: MailboxEnum.Junk }, + ]; + + for (const mailbox of mailboxesToSearch) { + if (!mailbox.id) continue; + + try { + await firstValueFrom(this.mailServerService.messages.getMessage(userId, mailbox.id, messageId)); + this.logger.log(`Message ${messageId} found in ${mailbox.name} mailbox`); + return { mailboxId: mailbox.id, mailboxName: mailbox.name }; + } catch (_error) { + continue; + } + } + + this.logger.warn(`Message ${messageId} not found in any mailbox for user ${userId}`); + return null; + } catch (error) { + this.logger.error(`Failed to find message ${messageId} for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`); + throw error; + } + } + //######################################################## async deleteMessage(userId: string, messageId: number) { try { - const inboxMailboxId = await this.mailboxResolverService.getInboxMailboxId(userId); - const result = await firstValueFrom(this.mailServerService.messages.deleteMessage(userId, inboxMailboxId, messageId)); + const messageLocation = await this.findMessageMailbox(userId, messageId); + + if (!messageLocation) throw new BadRequestException(EmailMessage.MESSAGE_NOT_FOUND); + + this.logger.log(`Deleting message ${messageId} from ${messageLocation.mailboxName} mailbox for user: ${userId}`); + + const result = await firstValueFrom(this.mailServerService.messages.deleteMessage(userId, messageLocation.mailboxId, messageId)); // Emit WebSocket notification const payload: EmailDeletePayload = { messageId, userId, - mailboxId: inboxMailboxId, - mailboxName: "Inbox", + mailboxId: messageLocation.mailboxId, + mailboxName: messageLocation.mailboxName, timestamp: new Date().toISOString(), }; await this.emailGateway.notifyMessageDeleted(payload); + this.logger.log(`Message ${messageId} deleted successfully from ${messageLocation.mailboxName}`); + return { success: true, message: EmailMessage.MESSAGE_DELETED_SUCCESSFULLY, @@ -158,19 +205,28 @@ export class EmailService { //============================================== async markMessageAsSeen(userId: string, messageId: number) { try { - const inboxMailboxId = await this.mailboxResolverService.getInboxMailboxId(userId); - const result = await firstValueFrom(this.mailServerService.messages.updateMessage(userId, inboxMailboxId, messageId, { seen: true })); + const messageLocation = await this.findMessageMailbox(userId, messageId); + + if (!messageLocation) throw new BadRequestException(EmailMessage.MESSAGE_NOT_FOUND); + + this.logger.log(`Marking message ${messageId} as seen in ${messageLocation.mailboxName} mailbox for user: ${userId}`); + + const result = await firstValueFrom( + this.mailServerService.messages.updateMessage(userId, messageLocation.mailboxId, messageId, { seen: true }), + ); // Emit WebSocket notification const payload: EmailStatusUpdatePayload = { messageId, userId, status: "read", - mailboxId: inboxMailboxId, + mailboxId: messageLocation.mailboxId, timestamp: new Date().toISOString(), }; await this.emailGateway.notifyMessageStatusUpdate(payload); + this.logger.log(`Message ${messageId} marked as seen successfully in ${messageLocation.mailboxName}`); + return { success: true, message: EmailMessage.MESSAGE_MARKED_AS_SEEN_SUCCESSFULLY, @@ -394,11 +450,16 @@ export class EmailService { this.logger.log(`Moving message ${messageId} to archive for user: ${userId}`); try { - const inboxMailboxId = await this.mailboxResolverService.getInboxMailboxId(userId); + const messageLocation = await this.findMessageMailbox(userId, messageId); + + if (!messageLocation) throw new BadRequestException(EmailMessage.MESSAGE_NOT_FOUND); + const archiveMailboxId = await this.mailboxResolverService.getArchiveMailboxId(userId); + this.logger.log(`Moving message ${messageId} from ${messageLocation.mailboxName} to ${MailboxEnum.ARCHIVE} for user: ${userId}`); + const result = await firstValueFrom( - this.mailServerService.messages.updateMessage(userId, inboxMailboxId, messageId, { + this.mailServerService.messages.updateMessage(userId, messageLocation.mailboxId, messageId, { moveTo: archiveMailboxId, }), ); @@ -407,15 +468,15 @@ export class EmailService { const payload: EmailMovePayload = { messageId, userId, - fromMailboxId: inboxMailboxId, + fromMailboxId: messageLocation.mailboxId, toMailboxId: archiveMailboxId, - fromMailboxName: "Inbox", - toMailboxName: "Archive", + fromMailboxName: messageLocation.mailboxName, + toMailboxName: MailboxEnum.ARCHIVE, timestamp: new Date().toISOString(), }; await this.emailGateway.notifyMessageMoved(payload); - this.logger.log(`Message ${messageId} moved to archive successfully`); + this.logger.log(`Message ${messageId} moved to archive successfully from ${messageLocation.mailboxName}`); return { success: true, @@ -435,11 +496,16 @@ export class EmailService { this.logger.log(`Moving message ${messageId} to favorite for user: ${userId}`); try { - const inboxMailboxId = await this.mailboxResolverService.getInboxMailboxId(userId); + const messageLocation = await this.findMessageMailbox(userId, messageId); + + if (!messageLocation) throw new BadRequestException(EmailMessage.MESSAGE_NOT_FOUND); + const favoriteMailboxId = await this.mailboxResolverService.getFavoriteMailboxId(userId); + this.logger.log(`Moving message ${messageId} from ${messageLocation.mailboxName} to ${MailboxEnum.FAVORITE} for user: ${userId}`); + const result = await firstValueFrom( - this.mailServerService.messages.updateMessage(userId, inboxMailboxId, messageId, { + this.mailServerService.messages.updateMessage(userId, messageLocation.mailboxId, messageId, { moveTo: favoriteMailboxId, }), ); @@ -448,15 +514,15 @@ export class EmailService { const payload: EmailMovePayload = { messageId, userId, - fromMailboxId: inboxMailboxId, + fromMailboxId: messageLocation.mailboxId, toMailboxId: favoriteMailboxId, - fromMailboxName: "Inbox", - toMailboxName: "Favorite", + fromMailboxName: messageLocation.mailboxName, + toMailboxName: MailboxEnum.FAVORITE, timestamp: new Date().toISOString(), }; await this.emailGateway.notifyMessageMoved(payload); - this.logger.log(`Message ${messageId} moved to favorite successfully`); + this.logger.log(`Message ${messageId} moved to favorite successfully from ${messageLocation.mailboxName}`); return { success: true, @@ -475,16 +541,33 @@ export class EmailService { this.logger.log(`Moving message ${messageId} to trash for user: ${userId}`); try { - const inboxMailboxId = await this.mailboxResolverService.getInboxMailboxId(userId); + const messageLocation = await this.findMessageMailbox(userId, messageId); + + if (!messageLocation) throw new BadRequestException(EmailMessage.MESSAGE_NOT_FOUND); + const trashMailboxId = await this.mailboxResolverService.getTrashMailboxId(userId); + this.logger.log(`Moving message ${messageId} from ${messageLocation.mailboxName} to ${MailboxEnum.TRASH} for user: ${userId}`); + const result = await firstValueFrom( - this.mailServerService.messages.updateMessage(userId, inboxMailboxId, messageId, { + this.mailServerService.messages.updateMessage(userId, messageLocation.mailboxId, messageId, { moveTo: trashMailboxId, }), ); - this.logger.log(`Message ${messageId} moved to trash successfully`); + // Emit WebSocket notification + const payload: EmailMovePayload = { + messageId, + userId, + fromMailboxId: messageLocation.mailboxId, + toMailboxId: trashMailboxId, + fromMailboxName: messageLocation.mailboxName, + toMailboxName: MailboxEnum.TRASH, + timestamp: new Date().toISOString(), + }; + await this.emailGateway.notifyMessageMoved(payload); + + this.logger.log(`Message ${messageId} moved to trash successfully from ${messageLocation.mailboxName}`); return { success: true, diff --git a/src/modules/email/services/mailbox-resolver.service.ts b/src/modules/email/services/mailbox-resolver.service.ts index 78c89b9..49a48ce 100644 --- a/src/modules/email/services/mailbox-resolver.service.ts +++ b/src/modules/email/services/mailbox-resolver.service.ts @@ -29,9 +29,7 @@ export class MailboxResolverService { */ async getUserMailboxIds(userId: string): Promise { const cached = this.getCachedMailboxIds(userId); - if (cached) { - return cached; - } + if (cached) return cached; this.logger.log(`Fetching mailbox IDs for user: ${userId}`);