chore: refactor search mehtod
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -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`);
|
||||
|
||||
@@ -5,5 +5,5 @@ export interface UserMailboxIds {
|
||||
trash: string;
|
||||
junk: 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) {
|
||||
try {
|
||||
|
||||
@@ -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<string, unknown> {
|
||||
const transformedQuery: Record<string, any> = { ...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];
|
||||
|
||||
@@ -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<string> {
|
||||
return this.getMailboxId(userId, "favorite");
|
||||
}
|
||||
// async getFavoriteMailboxId(userId: string): Promise<string> {
|
||||
// return this.getMailboxId(userId, "favorite");
|
||||
// }
|
||||
|
||||
/**
|
||||
* Clear cache for a specific user
|
||||
|
||||
@@ -26,7 +26,7 @@ export class UpdateMessageDto {
|
||||
expires?: string | false;
|
||||
}
|
||||
|
||||
export class SearchMessagesQueryDto {
|
||||
export class SearchMessagesMailServerQueryDto {
|
||||
q?: string;
|
||||
|
||||
mailbox?: string;
|
||||
|
||||
@@ -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[];
|
||||
|
||||
@@ -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<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
|
||||
*/
|
||||
searchMessages(userId: string, query: SearchMessagesQueryDto): Observable<MessageSearchResponse> {
|
||||
searchMessages(userId: string, query: SearchMessagesMailServerQueryDto): Observable<MessageSearchResponse> {
|
||||
return this.get<MessageSearchResponse>(`/users/${userId}/search`, query);
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user