diff --git a/src/common/enums/message.enum.ts b/src/common/enums/message.enum.ts index 49a82fa..ec992cc 100755 --- a/src/common/enums/message.enum.ts +++ b/src/common/enums/message.enum.ts @@ -241,9 +241,22 @@ export const enum EmailMessage { MESSAGE_NOT_FOUND = "پیام یافت نشد", // Bulk action messages - BULK_ACTION_COMPLETED_SUCCESSFULLY = "عملیات گروهی با موفقیت انجام شد", - BULK_ACTION_PARTIALLY_COMPLETED = "عملیات گروهی تا حدی انجام شد", - BULK_ACTION_FAILED = "عملیات گروهی با خطا مواجه شد", + BULK_ACTION_COMPLETED_SUCCESSFULLY = "[count] پیام با موفقیت [action] شد", + BULK_ACTION_PARTIALLY_COMPLETED = "[successful] از [total] پیام [action] شد. [failed] پیام با خطا مواجه شد", + BULK_ACTION_FAILED = "هیچ پیامی [action] نشد. تمام [total] تلاش ناموفق بود", + + // Bulk action display names + BULK_ACTION_SEEN = "خوانده شده علامت‌گذاری", + BULK_ACTION_DELETE = "حذف", + BULK_ACTION_ARCHIVE = "آرشیو", + BULK_ACTION_UNARCHIVE = "از آرشیو خارج", + BULK_ACTION_FAVORITE = "به علاقه مندی ها منتقل", + BULK_ACTION_UNFAVORITE = "از علاقه مندی ها حذف", + BULK_ACTION_JUNK = "به جانک منتقل", + BULK_ACTION_NOTJUNK = "از جانک از حذف", + BULK_ACTION_TRASH = "به سطل زباله منتقل", + BULK_ACTION_RESTORE = "از سطل زباله بازیابی", + BULK_ACTION_PROCESSED = "پردازش", USER_NOT_FOUND = "کاربر یافت نشد", PRIORITY_MUST_BE_ONE_OF_HIGH_NORMAL_LOW = "اولویت باید یکی از مقادیر high, normal, low باشد", SUBJECT_REQUIRED = "موضوع ایمیل مورد نیاز است", @@ -253,6 +266,10 @@ export const enum EmailMessage { MESSAGE_MARK_AS_UNSEEN_FAILED = "علامت گذاری پیام به عنوان خوانده نشده با خطا مواجه شد", ATTACHMENT_ID_MUST_BE_STRING = "شناسه پیوست باید یک رشته باشد", IS_FORWARD_MUST_BE_BOOLEAN = "IS_FORWARD_MUST_BE_BOOLEAN", + + // Favorite/Star messages (Gmail-like behavior) + MESSAGE_MARKED_AS_FAVORITE_SUCCESSFULLY = "پیام با موفقیت به عنوان مورد علاقه علامت گذاری شد", + MESSAGE_REMOVED_FROM_FAVORITE_SUCCESSFULLY = "پیام با موفقیت از مورد علاقه حذف شد", } export const enum WebSocketMessage { diff --git a/src/modules/domains/services/domain-automation.service.ts b/src/modules/domains/services/domain-automation.service.ts index 441da0b..95d6abf 100644 --- a/src/modules/domains/services/domain-automation.service.ts +++ b/src/modules/domains/services/domain-automation.service.ts @@ -62,7 +62,7 @@ export class DomainAutomationService { try { const mailboxesToCreate = [ { path: MailboxEnum.ARCHIVE, name: "Archive" }, - { path: MailboxEnum.FAVORITE, name: "Favorite" }, + // { path: MailboxEnum.STARRED, name: "Starred" }, ]; for (const mailbox of mailboxesToCreate) { diff --git a/src/modules/email/DTO/bulk-actions.dto.ts b/src/modules/email/DTO/bulk-actions.dto.ts index d8a4244..2887db0 100644 --- a/src/modules/email/DTO/bulk-actions.dto.ts +++ b/src/modules/email/DTO/bulk-actions.dto.ts @@ -35,6 +35,6 @@ export class BulkActionDto { enum: BulkActionType, }) @IsNotEmpty({ message: "Action type is required" }) - @IsEnum(BulkActionType, { message: "Action must be one of: seen, delete, archive, favorite, junk, trash" }) + @IsEnum(BulkActionType, { message: "Action must be one of: seen, delete, archive, favorite, unfavorite, junk, trash" }) action: BulkActionType; } diff --git a/src/modules/email/email.controller.ts b/src/modules/email/email.controller.ts index ead2819..830b661 100644 --- a/src/modules/email/email.controller.ts +++ b/src/modules/email/email.controller.ts @@ -137,11 +137,11 @@ export class EmailController { } @Get("messages/favorite") - @ApiOperation({ summary: "List messages in favorite mailbox" }) - @ApiResponse({ status: 200, description: "List of favorite messages" }) - @ApiResponse({ status: 404, description: "favorite mailbox not found" }) - getFavoriteMessages(@UserDec("wildduckUserId") userId: string, @Query() query: MessageListQueryDto) { - return this.emailService.getFavoriteMessages(userId, query); + @ApiOperation({ summary: "List messages in starred mailbox" }) + @ApiResponse({ status: 200, description: "List of starred messages" }) + @ApiResponse({ status: 404, description: "starred mailbox not found" }) + getStarredMessages(@UserDec("wildduckUserId") userId: string, @Query() query: MessageListQueryDto) { + return this.emailService.getStarredMessages(userId, query); } @Get("messages/drafts") @@ -189,15 +189,6 @@ export class EmailController { return this.emailService.moveMessageToArchive(userId, Number(params.messageId), query.mailbox); } - @Patch("messages/:messageId/favorite") - @ApiOperation({ summary: "Move a message to favorite mailbox" }) - @ApiResponse({ status: 200, description: "Message moved to favorite successfully" }) - @ApiResponse({ status: 404, description: "Message not found" }) - @ApiResponse({ status: 403, description: "Not authorized to move message" }) - moveMessageToFavorite(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) { - return this.emailService.moveMessageToFavorite(userId, Number(params.messageId), query.mailbox); - } - @Patch("messages/:messageId/trash") @ApiOperation({ summary: "Move a message to trash mailbox" }) @ApiResponse({ status: 200, description: "Message moved to trash successfully" }) @@ -216,20 +207,38 @@ export class EmailController { return this.emailService.moveMessageToInboxFromArchive(userId, Number(params.messageId), query.mailbox); } + @Patch("messages/:messageId/favorite") + @ApiOperation({ summary: "Star a message (Gmail-like behavior)" }) + @ApiResponse({ status: 200, description: "Message starred successfully" }) + @ApiResponse({ status: 404, description: "Message not found" }) + @ApiResponse({ status: 403, description: "Not authorized to star message" }) + starMessage(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) { + return this.emailService.starMessage(userId, Number(params.messageId), query.mailbox); + } + @Patch("messages/:messageId/unfavorite") - @ApiOperation({ summary: "Move a message from favorite back to inbox" }) - @ApiResponse({ status: 200, description: "Message moved from favorite to inbox successfully" }) - @ApiResponse({ status: 404, description: "Message not found in favorite" }) - @ApiResponse({ status: 403, description: "Not authorized to move message" }) - moveMessageToInboxFromFavorite(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) { - return this.emailService.moveMessageToInboxFromFavorite(userId, Number(params.messageId), query.mailbox); + @ApiOperation({ summary: "Unstar a message (Gmail-like behavior)" }) + @ApiResponse({ status: 200, description: "Message unstarred successfully" }) + @ApiResponse({ status: 404, description: "Message not found" }) + @ApiResponse({ status: 403, description: "Not authorized to unstar message" }) + unstarMessage(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) { + return this.emailService.unstarMessage(userId, Number(params.messageId), query.mailbox); + } + + @Patch("messages/:messageId/toggle-favorite") + @ApiOperation({ summary: "Toggle star status of a message (Gmail-like behavior)" }) + @ApiResponse({ status: 200, description: "Message star status toggled successfully" }) + @ApiResponse({ status: 404, description: "Message not found" }) + @ApiResponse({ status: 403, description: "Not authorized to modify message" }) + toggleMessageStar(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) { + return this.emailService.toggleMessageStar(userId, Number(params.messageId), query.mailbox); } @Patch("messages/:messageId/restore") - @ApiOperation({ summary: "Restore a message from trash back to inbox" }) - @ApiResponse({ status: 200, description: "Message restored from trash to inbox successfully" }) + @ApiOperation({ summary: "Move a message from trash back to inbox" }) + @ApiResponse({ status: 200, description: "Message restored to inbox successfully" }) @ApiResponse({ status: 404, description: "Message not found in trash" }) - @ApiResponse({ status: 403, description: "Not authorized to restore message" }) + @ApiResponse({ status: 403, description: "Not authorized to move message" }) moveMessageToInboxFromTrash(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) { return this.emailService.moveMessageToInboxFromTrash(userId, Number(params.messageId), query.mailbox); } @@ -264,51 +273,4 @@ export class EmailController { performBulkAction(@UserDec("wildduckUserId") userId: string, @Body() bulkActionDto: BulkActionDto, @Query() query: MailboxIdQueryDto) { return this.emailService.performBulkAction(userId, bulkActionDto, query.mailbox); } - - // // Domain Access Management Endpoints - // @Get("spam/domains/allowed") - // @ApiOperation({ summary: "List domains in allowlist (never marked as spam)" }) - // @ApiResponse({ status: 200, description: "List of allowed domains" }) - // listAllowedDomains(@UserDec("id") userId: string, @Query() query: ListDomainAccessQueryDto) { - // return this.emailSpamService.listAllowedDomains(userId, query); - // } - - // @Get("spam/domains/blocked") - // @ApiOperation({ summary: "List domains in blocklist (always marked as spam)" }) - // @ApiResponse({ status: 200, description: "List of blocked domains" }) - // listBlockedDomains(@UserDec("id") userId: string, @Query() query: ListDomainAccessQueryDto) { - // return this.emailSpamService.listBlockedDomains(userId, query); - // } - - // @Post("spam/domains/allowed") - // @ApiOperation({ summary: "Add domain to allowlist" }) - // @ApiResponse({ status: 201, description: "Domain added to allowlist successfully" }) - // @ApiResponse({ status: 400, description: "Invalid domain data" }) - // addDomainToAllowlist(@UserDec("id") userId: string, @Body() createDomainDto: CreateDomainAccessDto) { - // return this.emailSpamService.addDomainToAllowlist(createDomainDto.domain, createDomainDto.description, userId); - // } - - // @Post("spam/domains/blocked") - // @ApiOperation({ summary: "Add domain to blocklist" }) - // @ApiResponse({ status: 201, description: "Domain added to blocklist successfully" }) - // @ApiResponse({ status: 400, description: "Invalid domain data" }) - // addDomainToBlocklist(@UserDec("id") userId: string, @Body() createDomainDto: CreateDomainAccessDto) { - // return this.emailSpamService.addDomainToBlocklist(createDomainDto.domain, createDomainDto.description, userId); - // } - - // @Delete("spam/domains/allowed/:domain") - // @ApiOperation({ summary: "Remove domain from allowlist" }) - // @ApiResponse({ status: 200, description: "Domain removed from allowlist successfully" }) - // @ApiResponse({ status: 404, description: "Domain not found in allowlist" }) - // removeDomainFromAllowlist(@UserDec("id") userId: string, @Param("domain") domain: string) { - // return this.emailSpamService.removeDomainFromAllowlist(domain, userId); - // } - - // @Delete("spam/domains/blocked/:domain") - // @ApiOperation({ summary: "Remove domain from blocklist" }) - // @ApiResponse({ status: 200, description: "Domain removed from blocklist successfully" }) - // @ApiResponse({ status: 404, description: "Domain not found in blocklist" }) - // removeDomainFromBlocklist(@UserDec("id") userId: string, @Param("domain") domain: string) { - // return this.emailSpamService.removeDomainFromBlocklist(domain, userId); - // } } diff --git a/src/modules/email/gateways/email.gateway.ts b/src/modules/email/gateways/email.gateway.ts index d363084..89b6896 100644 --- a/src/modules/email/gateways/email.gateway.ts +++ b/src/modules/email/gateways/email.gateway.ts @@ -140,6 +140,16 @@ export class EmailGateway implements OnGatewayInit, OnGatewayConnection, OnGatew return await this.sendUserNotification(userId, WEBSOCKET_EVENTS.EMAIL_UNREAD, data, `Message ${data.messageId} marked as unread`); } + //************************************************************************ */ + async notifyEmailFlagged(userId: string, data: EmailStatusPayload) { + return await this.sendUserNotification(userId, WEBSOCKET_EVENTS.EMAIL_FLAGGED, data, `Message ${data.messageId} flagged`); + } + + //************************************************************************ */ + async notifyEmailUnflagged(userId: string, data: EmailStatusPayload) { + return await this.sendUserNotification(userId, WEBSOCKET_EVENTS.EMAIL_UNFLAGGED, data, `Message ${data.messageId} unflagged`); + } + //************************************************************************ */ async notifyEmailDeleted(userId: string, data: EmailDeletePayload) { return await this.sendUserNotification(userId, WEBSOCKET_EVENTS.EMAIL_DELETED, data, `Message ${data.messageId} deleted`); diff --git a/src/modules/email/interfaces/user-mailbox.interface.ts b/src/modules/email/interfaces/user-mailbox.interface.ts index 2f49327..69858db 100644 --- a/src/modules/email/interfaces/user-mailbox.interface.ts +++ b/src/modules/email/interfaces/user-mailbox.interface.ts @@ -5,5 +5,5 @@ export interface UserMailboxIds { trash: string; junk: string; archive: string; - favorite: string; + // starred: string; } diff --git a/src/modules/email/services/email-notification.service.ts b/src/modules/email/services/email-notification.service.ts index 5e7ae71..1b3e115 100644 --- a/src/modules/email/services/email-notification.service.ts +++ b/src/modules/email/services/email-notification.service.ts @@ -165,6 +165,76 @@ export class EmailNotificationService { } } + //************************************************************************ */ + 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 { diff --git a/src/modules/email/services/email.service.ts b/src/modules/email/services/email.service.ts index 4e0b52d..515ce08 100644 --- a/src/modules/email/services/email.service.ts +++ b/src/modules/email/services/email.service.ts @@ -7,6 +7,7 @@ import { EmailNotificationService } from "./email-notification.service"; import { EmailSpamService } from "./email-spam.service"; import { MailboxResolverService } from "./mailbox-resolver.service"; import { EmailMessage } from "../../../common/enums/message.enum"; +import { SearchMessagesMailServerQueryDto } from "../../mail-server/DTO/message.dto"; import { MailServerService } from "../../mail-server/services/mail-server.service"; import { TemplateProcessorService } from "../../templates/services/template-processor.service"; import { User } from "../../users/entities/user.entity"; @@ -102,20 +103,27 @@ export class EmailService { //######################################################## async searchMessages(wildduckUserId: string, query: SearchMessagesQueryDto) { - const wildDuckQuery = { + const wildDuckQuery: SearchMessagesMailServerQueryDto = { ...query, limit: query.limit || 20, page: query.page || 1, order: query.order || "desc", - query: query.search || query.q, - from: query.from || query.search, - to: query.to || query.search, - subject: query.subject || query.search, + mailbox: query.mailbox || "", }; + delete wildDuckQuery.mailbox; - if (wildDuckQuery.limit > 250) wildDuckQuery.limit = 250; - if (wildDuckQuery.limit < 1) wildDuckQuery.limit = 1; - if (wildDuckQuery.page < 1) wildDuckQuery.page = 1; + if (query.search) { + wildDuckQuery.query = query.search; + wildDuckQuery.from = query.search; + } + if (query.q) { + wildDuckQuery.query = query.q; + wildDuckQuery.from = query.q; + } + + if (query.from) wildDuckQuery.from = query.from; + if (query.to) wildDuckQuery.to = query.to; + if (query.subject) wildDuckQuery.subject = query.subject; const response = await firstValueFrom(this.mailServerService.messages.searchMessages(wildduckUserId, wildDuckQuery)); @@ -315,10 +323,17 @@ export class EmailService { } //============================================== - async getFavoriteMessages(wildduckUserId: string, query: MessageListQueryDto) { - const favoriteMailboxId = await this.mailboxResolverService.getFavoriteMailboxId(wildduckUserId); - const wildDuckQuery = this.transformPaginationQuery(query); - const response = await firstValueFrom(this.mailServerService.messages.listMessages(wildduckUserId, favoriteMailboxId, wildDuckQuery)); + //============================================== + async getStarredMessages(wildduckUserId: string, query: MessageListQueryDto) { + const searchQuery = { + ...query, + flagged: true, + limit: query.limit || 20, + page: query.page || 1, + order: query.order || "desc", + }; + + const response = await firstValueFrom(this.mailServerService.messages.searchMessages(wildduckUserId, searchQuery)); return { favoriteMails: response.results, @@ -451,23 +466,6 @@ export class EmailService { }; } - //============================================== - async moveMessageToFavorite(wildduckUserId: string, messageId: number, mailboxId: string) { - const favoriteMailboxId = await this.mailboxResolverService.getFavoriteMailboxId(wildduckUserId); - - const result = await firstValueFrom( - this.mailServerService.messages.updateMessage(wildduckUserId, mailboxId, messageId, { - moveTo: favoriteMailboxId, - }), - ); - - return { - success: true, - message: EmailMessage.MESSAGE_MOVED_TO_FAVORITE_SUCCESSFULLY, - result, - }; - } - //============================================== async moveMessageToTrash(wildduckUserId: string, messageId: number, mailboxId: string) { const trashMailboxId = await this.mailboxResolverService.getTrashMailboxId(wildduckUserId); @@ -503,22 +501,54 @@ export class EmailService { } //============================================== - async moveMessageToInboxFromFavorite(wildduckUserId: string, messageId: number, mailboxId: string) { - const inboxMailboxId = await this.mailboxResolverService.getInboxMailboxId(wildduckUserId); + async starMessage(wildduckUserId: 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, { - moveTo: inboxMailboxId, - }), - ); + await this.emailNotificationService.notifyEmailFlagged({ + userId: wildduckUserId, + messageId, + mailboxId, + }); return { success: true, - message: EmailMessage.MESSAGE_MOVED_TO_INBOX_FROM_FAVORITE_SUCCESSFULLY, + message: EmailMessage.MESSAGE_MARKED_AS_FAVORITE_SUCCESSFULLY, result, }; } + //============================================== + async unstarMessage(wildduckUserId: string, messageId: number, mailboxId: string) { + const result = await firstValueFrom( + this.mailServerService.messages.updateMessage(wildduckUserId, mailboxId, messageId, { + flagged: false, + }), + ); + + await this.emailNotificationService.notifyEmailUnflagged({ + userId: wildduckUserId, + messageId, + mailboxId, + }); + + return { + success: true, + message: EmailMessage.MESSAGE_REMOVED_FROM_FAVORITE_SUCCESSFULLY, + result, + }; + } + + //============================================== + async toggleMessageStar(wildduckUserId: string, messageId: number, mailboxId: string) { + const message = await firstValueFrom(this.mailServerService.messages.getMessage(wildduckUserId, mailboxId, messageId)); + + if (message.flagged) { + return this.unstarMessage(wildduckUserId, messageId, mailboxId); + } else { + return this.starMessage(wildduckUserId, messageId, mailboxId); + } + } + //============================================== async moveMessageToInboxFromTrash(wildduckUserId: string, messageId: number, mailboxId: string) { const inboxMailboxId = await this.mailboxResolverService.getInboxMailboxId(wildduckUserId); @@ -588,8 +618,6 @@ export class EmailService { }; } - //============================================== - // BULK ACTIONS //============================================== async performBulkAction(wildduckUserId: string, bulkActionDto: BulkActionDto, mailboxId: string) { const results = { @@ -597,7 +625,6 @@ export class EmailService { failed: [] as { messageId: number; error: string }[], }; - // Process each message ID for (const messageId of bulkActionDto.messageIds) { try { switch (bulkActionDto.action) { @@ -614,10 +641,10 @@ export class EmailService { await this.moveMessageToInboxFromArchive(wildduckUserId, messageId, mailboxId); break; case BulkActionType.FAVORITE: - await this.moveMessageToFavorite(wildduckUserId, messageId, mailboxId); + await this.starMessage(wildduckUserId, messageId, mailboxId); break; case BulkActionType.UNFAVORITE: - await this.moveMessageToInboxFromFavorite(wildduckUserId, messageId, mailboxId); + await this.unstarMessage(wildduckUserId, messageId, mailboxId); break; case BulkActionType.JUNK: await this.moveMessageToJunk(wildduckUserId, messageId, mailboxId); @@ -636,7 +663,6 @@ export class EmailService { } results.successful.push(messageId); - this.logger.log(`Successfully performed ${bulkActionDto.action} on message ${messageId}`); } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; results.failed.push({ messageId, error: errorMessage }); @@ -644,17 +670,22 @@ export class EmailService { } } - // Determine response message based on results let message: string; - if (results.failed.length === 0) { - message = EmailMessage.BULK_ACTION_COMPLETED_SUCCESSFULLY; - } else if (results.successful.length > 0) { - message = EmailMessage.BULK_ACTION_PARTIALLY_COMPLETED; - } else { - message = EmailMessage.BULK_ACTION_FAILED; - } + const actionName = this.getActionDisplayName(bulkActionDto.action); + const totalCount = bulkActionDto.messageIds.length; + const successCount = results.successful.length; + const failedCount = results.failed.length; - this.logger.log(`Bulk action ${bulkActionDto.action} completed: ${results.successful.length} successful, ${results.failed.length} failed`); + if (results.failed.length === 0) { + message = EmailMessage.BULK_ACTION_COMPLETED_SUCCESSFULLY.replace("[count]", totalCount.toString()).replace("[action]", actionName); + } else if (results.successful.length > 0) { + message = EmailMessage.BULK_ACTION_PARTIALLY_COMPLETED.replace("[successful]", successCount.toString()) + .replace("[total]", totalCount.toString()) + .replace("[action]", actionName) + .replace("[failed]", failedCount.toString()); + } else { + message = EmailMessage.BULK_ACTION_FAILED.replace("[action]", actionName).replace("[total]", totalCount.toString()); + } return { success: results.failed.length === 0, @@ -671,24 +702,47 @@ export class EmailService { //######################################################## + private getActionDisplayName(action: BulkActionType): string { + switch (action) { + case BulkActionType.SEEN: + return EmailMessage.BULK_ACTION_SEEN; + case BulkActionType.DELETE: + return EmailMessage.BULK_ACTION_DELETE; + case BulkActionType.ARCHIVE: + return EmailMessage.BULK_ACTION_ARCHIVE; + case BulkActionType.UNARCHIVE: + return EmailMessage.BULK_ACTION_UNARCHIVE; + case BulkActionType.FAVORITE: + return EmailMessage.BULK_ACTION_FAVORITE; + case BulkActionType.UNFAVORITE: + return EmailMessage.BULK_ACTION_UNFAVORITE; + case BulkActionType.JUNK: + return EmailMessage.BULK_ACTION_JUNK; + case BulkActionType.NOTJUNK: + return EmailMessage.BULK_ACTION_NOTJUNK; + case BulkActionType.TRASH: + return EmailMessage.BULK_ACTION_TRASH; + case BulkActionType.RESTORE: + return EmailMessage.BULK_ACTION_RESTORE; + default: + return EmailMessage.BULK_ACTION_PROCESSED; + } + } + + //============================================== private transformPaginationQuery(query: MessageListQueryDto): Record { const transformedQuery: Record = { ...query }; - // Set default values if not provided if (!transformedQuery.page) transformedQuery.page = 1; if (!transformedQuery.limit) transformedQuery.limit = 20; - // Ensure limit is within WildDuck's bounds (1-250) if (transformedQuery.limit > 250) transformedQuery.limit = 250; if (transformedQuery.limit < 1) transformedQuery.limit = 1; - // Ensure page is at least 1 if (transformedQuery.page < 1) transformedQuery.page = 1; - // Set default order if not provided if (!transformedQuery.order) transformedQuery.order = "desc"; - // Remove undefined/null values Object.keys(transformedQuery).forEach((key) => { if (transformedQuery[key] === undefined || transformedQuery[key] === null) { delete transformedQuery[key]; diff --git a/src/modules/email/services/mailbox-resolver.service.ts b/src/modules/email/services/mailbox-resolver.service.ts index d0c5de5..19bd3e1 100644 --- a/src/modules/email/services/mailbox-resolver.service.ts +++ b/src/modules/email/services/mailbox-resolver.service.ts @@ -34,7 +34,7 @@ export class MailboxResolverService { trash: this.findMailboxId(mailboxes.results, MailboxEnum.TRASH), junk: this.findMailboxId(mailboxes.results, MailboxEnum.Junk, false), archive: this.findMailboxId(mailboxes.results, MailboxEnum.ARCHIVE, false), - favorite: this.findMailboxId(mailboxes.results, MailboxEnum.FAVORITE, false), + // favorite: this.findMailboxId(mailboxes.results, MailboxEnum.FAVORITE, false), }; this.cacheMailboxIds(userId, mailboxIds); @@ -106,9 +106,9 @@ export class MailboxResolverService { return this.getMailboxId(userId, "archive"); } - async getFavoriteMailboxId(userId: string): Promise { - return this.getMailboxId(userId, "favorite"); - } + // async getFavoriteMailboxId(userId: string): Promise { + // return this.getMailboxId(userId, "favorite"); + // } /** * Clear cache for a specific user diff --git a/src/modules/mail-server/DTO/message.dto.ts b/src/modules/mail-server/DTO/message.dto.ts index 49cd6ac..1955434 100644 --- a/src/modules/mail-server/DTO/message.dto.ts +++ b/src/modules/mail-server/DTO/message.dto.ts @@ -26,7 +26,7 @@ export class UpdateMessageDto { expires?: string | false; } -export class SearchMessagesQueryDto { +export class SearchMessagesMailServerQueryDto { q?: string; mailbox?: string; diff --git a/src/modules/mail-server/interfaces/messages-response.interface.ts b/src/modules/mail-server/interfaces/messages-response.interface.ts index c8669b7..11c9934 100644 --- a/src/modules/mail-server/interfaces/messages-response.interface.ts +++ b/src/modules/mail-server/interfaces/messages-response.interface.ts @@ -122,15 +122,6 @@ export interface MessageSearchResponse { query: string; } -export interface MessageFlaggedResponse { - success: true; - total: number; - page: number; - previousCursor: string | false; - nextCursor: string | false; - results: MessageDetails[]; -} - export interface MessageUpdateResponse { success: true; id: string[]; diff --git a/src/modules/mail-server/services/wildduck-messages.service.ts b/src/modules/mail-server/services/wildduck-messages.service.ts index 19158c0..976ef2e 100644 --- a/src/modules/mail-server/services/wildduck-messages.service.ts +++ b/src/modules/mail-server/services/wildduck-messages.service.ts @@ -4,13 +4,12 @@ import { catchError, map, throwError } from "rxjs"; import { WildDuckBaseService } from "./wildduck-base.service"; import { MailServerException } from "../../../core/exceptions/mail-server.exceptions"; -import { ListMessagesQueryDto, SearchMessagesQueryDto, UpdateMessageDto, UploadMessageDto } from "../DTO/message.dto"; +import { ListMessagesQueryDto, SearchMessagesMailServerQueryDto, UpdateMessageDto, UploadMessageDto } from "../DTO/message.dto"; import { AttachmentResponse } from "../interfaces/attachment.interface"; import { MessageDeleteAllResponse, MessageDetails, MessageEventsResponse, - MessageFlaggedResponse, MessageForwardResponse, MessageListResponse, MessageSearchResponse, @@ -117,17 +116,10 @@ export class WildDuckMessagesService extends WildDuckBaseService { return this.get(`/users/${userId}/mailboxes/${mailboxId}/messages/${messageId}/events`); } - /** - * List flagged messages across all mailboxes - */ - listFlaggedMessages(userId: string, query?: { order?: "asc" | "desc"; limit?: number; page?: number }): Observable { - return this.get(`/users/${userId}/flagged`, query); - } - /** * Search for messages across all mailboxes */ - searchMessages(userId: string, query: SearchMessagesQueryDto): Observable { + searchMessages(userId: string, query: SearchMessagesMailServerQueryDto): Observable { return this.get(`/users/${userId}/search`, query); } diff --git a/src/modules/mailbox/enums/mailbox.enum.ts b/src/modules/mailbox/enums/mailbox.enum.ts index 366597c..54890cb 100644 --- a/src/modules/mailbox/enums/mailbox.enum.ts +++ b/src/modules/mailbox/enums/mailbox.enum.ts @@ -2,7 +2,7 @@ export enum MailboxEnum { INBOX = "INBOX", DRAFTS = "Drafts", ARCHIVE = "Archive", //should created when user created - FAVORITE = "FAVORITE", //should created when user created + // STARRED = "Starred", //should created when user created Junk = "Junk", SENT = "Sent Mail", TRASH = "Trash",