chore: refactor search mehtod
This commit is contained in:
@@ -241,9 +241,22 @@ export const enum EmailMessage {
|
|||||||
MESSAGE_NOT_FOUND = "پیام یافت نشد",
|
MESSAGE_NOT_FOUND = "پیام یافت نشد",
|
||||||
|
|
||||||
// Bulk action messages
|
// Bulk action messages
|
||||||
BULK_ACTION_COMPLETED_SUCCESSFULLY = "عملیات گروهی با موفقیت انجام شد",
|
BULK_ACTION_COMPLETED_SUCCESSFULLY = "[count] پیام با موفقیت [action] شد",
|
||||||
BULK_ACTION_PARTIALLY_COMPLETED = "عملیات گروهی تا حدی انجام شد",
|
BULK_ACTION_PARTIALLY_COMPLETED = "[successful] از [total] پیام [action] شد. [failed] پیام با خطا مواجه شد",
|
||||||
BULK_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 = "کاربر یافت نشد",
|
USER_NOT_FOUND = "کاربر یافت نشد",
|
||||||
PRIORITY_MUST_BE_ONE_OF_HIGH_NORMAL_LOW = "اولویت باید یکی از مقادیر high, normal, low باشد",
|
PRIORITY_MUST_BE_ONE_OF_HIGH_NORMAL_LOW = "اولویت باید یکی از مقادیر high, normal, low باشد",
|
||||||
SUBJECT_REQUIRED = "موضوع ایمیل مورد نیاز است",
|
SUBJECT_REQUIRED = "موضوع ایمیل مورد نیاز است",
|
||||||
@@ -253,6 +266,10 @@ export const enum EmailMessage {
|
|||||||
MESSAGE_MARK_AS_UNSEEN_FAILED = "علامت گذاری پیام به عنوان خوانده نشده با خطا مواجه شد",
|
MESSAGE_MARK_AS_UNSEEN_FAILED = "علامت گذاری پیام به عنوان خوانده نشده با خطا مواجه شد",
|
||||||
ATTACHMENT_ID_MUST_BE_STRING = "شناسه پیوست باید یک رشته باشد",
|
ATTACHMENT_ID_MUST_BE_STRING = "شناسه پیوست باید یک رشته باشد",
|
||||||
IS_FORWARD_MUST_BE_BOOLEAN = "IS_FORWARD_MUST_BE_BOOLEAN",
|
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 {
|
export const enum WebSocketMessage {
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export class DomainAutomationService {
|
|||||||
try {
|
try {
|
||||||
const mailboxesToCreate = [
|
const mailboxesToCreate = [
|
||||||
{ path: MailboxEnum.ARCHIVE, name: "Archive" },
|
{ path: MailboxEnum.ARCHIVE, name: "Archive" },
|
||||||
{ path: MailboxEnum.FAVORITE, name: "Favorite" },
|
// { path: MailboxEnum.STARRED, name: "Starred" },
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const mailbox of mailboxesToCreate) {
|
for (const mailbox of mailboxesToCreate) {
|
||||||
|
|||||||
@@ -35,6 +35,6 @@ export class BulkActionDto {
|
|||||||
enum: BulkActionType,
|
enum: BulkActionType,
|
||||||
})
|
})
|
||||||
@IsNotEmpty({ message: "Action type is required" })
|
@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;
|
action: BulkActionType;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,11 +137,11 @@ export class EmailController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Get("messages/favorite")
|
@Get("messages/favorite")
|
||||||
@ApiOperation({ summary: "List messages in favorite mailbox" })
|
@ApiOperation({ summary: "List messages in starred mailbox" })
|
||||||
@ApiResponse({ status: 200, description: "List of favorite messages" })
|
@ApiResponse({ status: 200, description: "List of starred messages" })
|
||||||
@ApiResponse({ status: 404, description: "favorite mailbox not found" })
|
@ApiResponse({ status: 404, description: "starred mailbox not found" })
|
||||||
getFavoriteMessages(@UserDec("wildduckUserId") userId: string, @Query() query: MessageListQueryDto) {
|
getStarredMessages(@UserDec("wildduckUserId") userId: string, @Query() query: MessageListQueryDto) {
|
||||||
return this.emailService.getFavoriteMessages(userId, query);
|
return this.emailService.getStarredMessages(userId, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get("messages/drafts")
|
@Get("messages/drafts")
|
||||||
@@ -189,15 +189,6 @@ export class EmailController {
|
|||||||
return this.emailService.moveMessageToArchive(userId, Number(params.messageId), query.mailbox);
|
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")
|
@Patch("messages/:messageId/trash")
|
||||||
@ApiOperation({ summary: "Move a message to trash mailbox" })
|
@ApiOperation({ summary: "Move a message to trash mailbox" })
|
||||||
@ApiResponse({ status: 200, description: "Message moved to trash successfully" })
|
@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);
|
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")
|
@Patch("messages/:messageId/unfavorite")
|
||||||
@ApiOperation({ summary: "Move a message from favorite back to inbox" })
|
@ApiOperation({ summary: "Unstar a message (Gmail-like behavior)" })
|
||||||
@ApiResponse({ status: 200, description: "Message moved from favorite to inbox successfully" })
|
@ApiResponse({ status: 200, description: "Message unstarred successfully" })
|
||||||
@ApiResponse({ status: 404, description: "Message not found in favorite" })
|
@ApiResponse({ status: 404, description: "Message not found" })
|
||||||
@ApiResponse({ status: 403, description: "Not authorized to move message" })
|
@ApiResponse({ status: 403, description: "Not authorized to unstar message" })
|
||||||
moveMessageToInboxFromFavorite(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
unstarMessage(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
||||||
return this.emailService.moveMessageToInboxFromFavorite(userId, Number(params.messageId), query.mailbox);
|
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")
|
@Patch("messages/:messageId/restore")
|
||||||
@ApiOperation({ summary: "Restore a message from trash back to inbox" })
|
@ApiOperation({ summary: "Move a message from trash back to inbox" })
|
||||||
@ApiResponse({ status: 200, description: "Message restored from trash to inbox successfully" })
|
@ApiResponse({ status: 200, description: "Message restored to inbox successfully" })
|
||||||
@ApiResponse({ status: 404, description: "Message not found in trash" })
|
@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) {
|
moveMessageToInboxFromTrash(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
||||||
return this.emailService.moveMessageToInboxFromTrash(userId, Number(params.messageId), query.mailbox);
|
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) {
|
performBulkAction(@UserDec("wildduckUserId") userId: string, @Body() bulkActionDto: BulkActionDto, @Query() query: MailboxIdQueryDto) {
|
||||||
return this.emailService.performBulkAction(userId, bulkActionDto, query.mailbox);
|
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);
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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`);
|
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) {
|
async notifyEmailDeleted(userId: string, data: EmailDeletePayload) {
|
||||||
return await this.sendUserNotification(userId, WEBSOCKET_EVENTS.EMAIL_DELETED, data, `Message ${data.messageId} deleted`);
|
return await this.sendUserNotification(userId, WEBSOCKET_EVENTS.EMAIL_DELETED, data, `Message ${data.messageId} deleted`);
|
||||||
|
|||||||
@@ -5,5 +5,5 @@ export interface UserMailboxIds {
|
|||||||
trash: string;
|
trash: string;
|
||||||
junk: string;
|
junk: string;
|
||||||
archive: string;
|
archive: string;
|
||||||
favorite: string;
|
// starred: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
async notifyMailboxUpdate(params: MailboxUpdateNotificationParams) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { EmailNotificationService } from "./email-notification.service";
|
|||||||
import { EmailSpamService } from "./email-spam.service";
|
import { EmailSpamService } from "./email-spam.service";
|
||||||
import { MailboxResolverService } from "./mailbox-resolver.service";
|
import { MailboxResolverService } from "./mailbox-resolver.service";
|
||||||
import { EmailMessage } from "../../../common/enums/message.enum";
|
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 { MailServerService } from "../../mail-server/services/mail-server.service";
|
||||||
import { TemplateProcessorService } from "../../templates/services/template-processor.service";
|
import { TemplateProcessorService } from "../../templates/services/template-processor.service";
|
||||||
import { User } from "../../users/entities/user.entity";
|
import { User } from "../../users/entities/user.entity";
|
||||||
@@ -102,20 +103,27 @@ export class EmailService {
|
|||||||
|
|
||||||
//########################################################
|
//########################################################
|
||||||
async searchMessages(wildduckUserId: string, query: SearchMessagesQueryDto) {
|
async searchMessages(wildduckUserId: string, query: SearchMessagesQueryDto) {
|
||||||
const wildDuckQuery = {
|
const wildDuckQuery: SearchMessagesMailServerQueryDto = {
|
||||||
...query,
|
...query,
|
||||||
limit: query.limit || 20,
|
limit: query.limit || 20,
|
||||||
page: query.page || 1,
|
page: query.page || 1,
|
||||||
order: query.order || "desc",
|
order: query.order || "desc",
|
||||||
query: query.search || query.q,
|
mailbox: query.mailbox || "",
|
||||||
from: query.from || query.search,
|
|
||||||
to: query.to || query.search,
|
|
||||||
subject: query.subject || query.search,
|
|
||||||
};
|
};
|
||||||
|
delete wildDuckQuery.mailbox;
|
||||||
|
|
||||||
if (wildDuckQuery.limit > 250) wildDuckQuery.limit = 250;
|
if (query.search) {
|
||||||
if (wildDuckQuery.limit < 1) wildDuckQuery.limit = 1;
|
wildDuckQuery.query = query.search;
|
||||||
if (wildDuckQuery.page < 1) wildDuckQuery.page = 1;
|
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));
|
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);
|
async getStarredMessages(wildduckUserId: string, query: MessageListQueryDto) {
|
||||||
const wildDuckQuery = this.transformPaginationQuery(query);
|
const searchQuery = {
|
||||||
const response = await firstValueFrom(this.mailServerService.messages.listMessages(wildduckUserId, favoriteMailboxId, wildDuckQuery));
|
...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 {
|
return {
|
||||||
favoriteMails: response.results,
|
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) {
|
async moveMessageToTrash(wildduckUserId: string, messageId: number, mailboxId: string) {
|
||||||
const trashMailboxId = await this.mailboxResolverService.getTrashMailboxId(wildduckUserId);
|
const trashMailboxId = await this.mailboxResolverService.getTrashMailboxId(wildduckUserId);
|
||||||
@@ -503,22 +501,54 @@ export class EmailService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//==============================================
|
//==============================================
|
||||||
async moveMessageToInboxFromFavorite(wildduckUserId: string, messageId: number, mailboxId: string) {
|
async starMessage(wildduckUserId: string, messageId: number, mailboxId: string) {
|
||||||
const inboxMailboxId = await this.mailboxResolverService.getInboxMailboxId(wildduckUserId);
|
const result = await firstValueFrom(this.mailServerService.messages.updateMessage(wildduckUserId, mailboxId, messageId, { flagged: true }));
|
||||||
|
|
||||||
const result = await firstValueFrom(
|
await this.emailNotificationService.notifyEmailFlagged({
|
||||||
this.mailServerService.messages.updateMessage(wildduckUserId, mailboxId, messageId, {
|
userId: wildduckUserId,
|
||||||
moveTo: inboxMailboxId,
|
messageId,
|
||||||
}),
|
mailboxId,
|
||||||
);
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: EmailMessage.MESSAGE_MOVED_TO_INBOX_FROM_FAVORITE_SUCCESSFULLY,
|
message: EmailMessage.MESSAGE_MARKED_AS_FAVORITE_SUCCESSFULLY,
|
||||||
result,
|
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) {
|
async moveMessageToInboxFromTrash(wildduckUserId: string, messageId: number, mailboxId: string) {
|
||||||
const inboxMailboxId = await this.mailboxResolverService.getInboxMailboxId(wildduckUserId);
|
const inboxMailboxId = await this.mailboxResolverService.getInboxMailboxId(wildduckUserId);
|
||||||
@@ -588,8 +618,6 @@ export class EmailService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
//==============================================
|
|
||||||
// BULK ACTIONS
|
|
||||||
//==============================================
|
//==============================================
|
||||||
async performBulkAction(wildduckUserId: string, bulkActionDto: BulkActionDto, mailboxId: string) {
|
async performBulkAction(wildduckUserId: string, bulkActionDto: BulkActionDto, mailboxId: string) {
|
||||||
const results = {
|
const results = {
|
||||||
@@ -597,7 +625,6 @@ export class EmailService {
|
|||||||
failed: [] as { messageId: number; error: string }[],
|
failed: [] as { messageId: number; error: string }[],
|
||||||
};
|
};
|
||||||
|
|
||||||
// Process each message ID
|
|
||||||
for (const messageId of bulkActionDto.messageIds) {
|
for (const messageId of bulkActionDto.messageIds) {
|
||||||
try {
|
try {
|
||||||
switch (bulkActionDto.action) {
|
switch (bulkActionDto.action) {
|
||||||
@@ -614,10 +641,10 @@ export class EmailService {
|
|||||||
await this.moveMessageToInboxFromArchive(wildduckUserId, messageId, mailboxId);
|
await this.moveMessageToInboxFromArchive(wildduckUserId, messageId, mailboxId);
|
||||||
break;
|
break;
|
||||||
case BulkActionType.FAVORITE:
|
case BulkActionType.FAVORITE:
|
||||||
await this.moveMessageToFavorite(wildduckUserId, messageId, mailboxId);
|
await this.starMessage(wildduckUserId, messageId, mailboxId);
|
||||||
break;
|
break;
|
||||||
case BulkActionType.UNFAVORITE:
|
case BulkActionType.UNFAVORITE:
|
||||||
await this.moveMessageToInboxFromFavorite(wildduckUserId, messageId, mailboxId);
|
await this.unstarMessage(wildduckUserId, messageId, mailboxId);
|
||||||
break;
|
break;
|
||||||
case BulkActionType.JUNK:
|
case BulkActionType.JUNK:
|
||||||
await this.moveMessageToJunk(wildduckUserId, messageId, mailboxId);
|
await this.moveMessageToJunk(wildduckUserId, messageId, mailboxId);
|
||||||
@@ -636,7 +663,6 @@ export class EmailService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
results.successful.push(messageId);
|
results.successful.push(messageId);
|
||||||
this.logger.log(`Successfully performed ${bulkActionDto.action} on message ${messageId}`);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
||||||
results.failed.push({ messageId, error: errorMessage });
|
results.failed.push({ messageId, error: errorMessage });
|
||||||
@@ -644,17 +670,22 @@ export class EmailService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine response message based on results
|
|
||||||
let message: string;
|
let message: string;
|
||||||
if (results.failed.length === 0) {
|
const actionName = this.getActionDisplayName(bulkActionDto.action);
|
||||||
message = EmailMessage.BULK_ACTION_COMPLETED_SUCCESSFULLY;
|
const totalCount = bulkActionDto.messageIds.length;
|
||||||
} else if (results.successful.length > 0) {
|
const successCount = results.successful.length;
|
||||||
message = EmailMessage.BULK_ACTION_PARTIALLY_COMPLETED;
|
const failedCount = results.failed.length;
|
||||||
} else {
|
|
||||||
message = EmailMessage.BULK_ACTION_FAILED;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
return {
|
||||||
success: results.failed.length === 0,
|
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<string, unknown> {
|
private transformPaginationQuery(query: MessageListQueryDto): Record<string, unknown> {
|
||||||
const transformedQuery: Record<string, any> = { ...query };
|
const transformedQuery: Record<string, any> = { ...query };
|
||||||
|
|
||||||
// Set default values if not provided
|
|
||||||
if (!transformedQuery.page) transformedQuery.page = 1;
|
if (!transformedQuery.page) transformedQuery.page = 1;
|
||||||
if (!transformedQuery.limit) transformedQuery.limit = 20;
|
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 > 250) transformedQuery.limit = 250;
|
||||||
if (transformedQuery.limit < 1) transformedQuery.limit = 1;
|
if (transformedQuery.limit < 1) transformedQuery.limit = 1;
|
||||||
|
|
||||||
// Ensure page is at least 1
|
|
||||||
if (transformedQuery.page < 1) transformedQuery.page = 1;
|
if (transformedQuery.page < 1) transformedQuery.page = 1;
|
||||||
|
|
||||||
// Set default order if not provided
|
|
||||||
if (!transformedQuery.order) transformedQuery.order = "desc";
|
if (!transformedQuery.order) transformedQuery.order = "desc";
|
||||||
|
|
||||||
// Remove undefined/null values
|
|
||||||
Object.keys(transformedQuery).forEach((key) => {
|
Object.keys(transformedQuery).forEach((key) => {
|
||||||
if (transformedQuery[key] === undefined || transformedQuery[key] === null) {
|
if (transformedQuery[key] === undefined || transformedQuery[key] === null) {
|
||||||
delete transformedQuery[key];
|
delete transformedQuery[key];
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export class MailboxResolverService {
|
|||||||
trash: this.findMailboxId(mailboxes.results, MailboxEnum.TRASH),
|
trash: this.findMailboxId(mailboxes.results, MailboxEnum.TRASH),
|
||||||
junk: this.findMailboxId(mailboxes.results, MailboxEnum.Junk, false),
|
junk: this.findMailboxId(mailboxes.results, MailboxEnum.Junk, false),
|
||||||
archive: this.findMailboxId(mailboxes.results, MailboxEnum.ARCHIVE, 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);
|
this.cacheMailboxIds(userId, mailboxIds);
|
||||||
@@ -106,9 +106,9 @@ export class MailboxResolverService {
|
|||||||
return this.getMailboxId(userId, "archive");
|
return this.getMailboxId(userId, "archive");
|
||||||
}
|
}
|
||||||
|
|
||||||
async getFavoriteMailboxId(userId: string): Promise<string> {
|
// async getFavoriteMailboxId(userId: string): Promise<string> {
|
||||||
return this.getMailboxId(userId, "favorite");
|
// return this.getMailboxId(userId, "favorite");
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear cache for a specific user
|
* Clear cache for a specific user
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export class UpdateMessageDto {
|
|||||||
expires?: string | false;
|
expires?: string | false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SearchMessagesQueryDto {
|
export class SearchMessagesMailServerQueryDto {
|
||||||
q?: string;
|
q?: string;
|
||||||
|
|
||||||
mailbox?: string;
|
mailbox?: string;
|
||||||
|
|||||||
@@ -122,15 +122,6 @@ export interface MessageSearchResponse {
|
|||||||
query: string;
|
query: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MessageFlaggedResponse {
|
|
||||||
success: true;
|
|
||||||
total: number;
|
|
||||||
page: number;
|
|
||||||
previousCursor: string | false;
|
|
||||||
nextCursor: string | false;
|
|
||||||
results: MessageDetails[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MessageUpdateResponse {
|
export interface MessageUpdateResponse {
|
||||||
success: true;
|
success: true;
|
||||||
id: string[];
|
id: string[];
|
||||||
|
|||||||
@@ -4,13 +4,12 @@ import { catchError, map, throwError } from "rxjs";
|
|||||||
|
|
||||||
import { WildDuckBaseService } from "./wildduck-base.service";
|
import { WildDuckBaseService } from "./wildduck-base.service";
|
||||||
import { MailServerException } from "../../../core/exceptions/mail-server.exceptions";
|
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 { AttachmentResponse } from "../interfaces/attachment.interface";
|
||||||
import {
|
import {
|
||||||
MessageDeleteAllResponse,
|
MessageDeleteAllResponse,
|
||||||
MessageDetails,
|
MessageDetails,
|
||||||
MessageEventsResponse,
|
MessageEventsResponse,
|
||||||
MessageFlaggedResponse,
|
|
||||||
MessageForwardResponse,
|
MessageForwardResponse,
|
||||||
MessageListResponse,
|
MessageListResponse,
|
||||||
MessageSearchResponse,
|
MessageSearchResponse,
|
||||||
@@ -117,17 +116,10 @@ export class WildDuckMessagesService extends WildDuckBaseService {
|
|||||||
return this.get<MessageEventsResponse>(`/users/${userId}/mailboxes/${mailboxId}/messages/${messageId}/events`);
|
return this.get<MessageEventsResponse>(`/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<MessageFlaggedResponse> {
|
|
||||||
return this.get<MessageFlaggedResponse>(`/users/${userId}/flagged`, query);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search for messages across all mailboxes
|
* Search for messages across all mailboxes
|
||||||
*/
|
*/
|
||||||
searchMessages(userId: string, query: SearchMessagesQueryDto): Observable<MessageSearchResponse> {
|
searchMessages(userId: string, query: SearchMessagesMailServerQueryDto): Observable<MessageSearchResponse> {
|
||||||
return this.get<MessageSearchResponse>(`/users/${userId}/search`, query);
|
return this.get<MessageSearchResponse>(`/users/${userId}/search`, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ export enum MailboxEnum {
|
|||||||
INBOX = "INBOX",
|
INBOX = "INBOX",
|
||||||
DRAFTS = "Drafts",
|
DRAFTS = "Drafts",
|
||||||
ARCHIVE = "Archive", //should created when user created
|
ARCHIVE = "Archive", //should created when user created
|
||||||
FAVORITE = "FAVORITE", //should created when user created
|
// STARRED = "Starred", //should created when user created
|
||||||
Junk = "Junk",
|
Junk = "Junk",
|
||||||
SENT = "Sent Mail",
|
SENT = "Sent Mail",
|
||||||
TRASH = "Trash",
|
TRASH = "Trash",
|
||||||
|
|||||||
Reference in New Issue
Block a user