From 784f168e8c7af65d115da0974b0939f7293e487f Mon Sep 17 00:00:00 2001 From: mahyargdz Date: Wed, 9 Jul 2025 16:59:26 +0330 Subject: [PATCH] chore: changes --- src/common/decorators/user.decorator.ts | 2 +- src/common/enums/message.enum.ts | 8 ++ .../auth/strategies/local-jwt.strategy.ts | 11 ++- src/modules/email/DTO/email-params.dto.ts | 4 +- src/modules/email/email.controller.ts | 40 +++++++-- src/modules/email/services/email.service.ts | 86 +++++++++++++++---- 6 files changed, 120 insertions(+), 31 deletions(-) diff --git a/src/common/decorators/user.decorator.ts b/src/common/decorators/user.decorator.ts index 112b82e..dc89df4 100755 --- a/src/common/decorators/user.decorator.ts +++ b/src/common/decorators/user.decorator.ts @@ -9,7 +9,7 @@ declare module "fastify" { } } -export const UserDec = createParamDecorator((data: keyof ITokenPayload | undefined, ctx: ExecutionContext) => { +export const UserDec = createParamDecorator((data: keyof ITokenPayload, ctx: ExecutionContext) => { const req = ctx.switchToHttp().getRequest(); const user = req.user; diff --git a/src/common/enums/message.enum.ts b/src/common/enums/message.enum.ts index e76a688..b9d23fc 100755 --- a/src/common/enums/message.enum.ts +++ b/src/common/enums/message.enum.ts @@ -186,6 +186,14 @@ export const enum EmailMessage { SEND_TIME_MUST_BE_DATE = "زمان ارسال باید یک تاریخ معتبر باشد", UPLOAD_ONLY_MUST_BE_BOOLEAN = "حالت فقط آپلود باید بولین باشد", ENVELOPE_MUST_BE_VALID = "envelope باید معتبر باشد", + + // Message operations + MESSAGE_MARKED_AS_SEEN_SUCCESSFULLY = "پیام با موفقیت به عنوان خوانده شده علامت گذاری شد", + MESSAGE_MARK_AS_SEEN_FAILED = "علامت گذاری پیام با خطا مواجه شد", + SENT_MESSAGES_RETRIEVED_SUCCESSFULLY = "پیام های ارسالی با موفقیت دریافت شد", + SENT_MESSAGES_RETRIEVAL_FAILED = "دریافت پیام های ارسالی با خطا مواجه شد", + TRASH_MESSAGES_RETRIEVED_SUCCESSFULLY = "پیام های حذف شده با موفقیت دریافت شد", + TRASH_MESSAGES_RETRIEVAL_FAILED = "دریافت پیام های حذف شده با خطا مواجه شد", } export const enum SmsMessage { diff --git a/src/modules/auth/strategies/local-jwt.strategy.ts b/src/modules/auth/strategies/local-jwt.strategy.ts index 27aa915..c655014 100644 --- a/src/modules/auth/strategies/local-jwt.strategy.ts +++ b/src/modules/auth/strategies/local-jwt.strategy.ts @@ -18,6 +18,15 @@ export class LocalJwtStrategy extends PassportStrategy(Strategy, LOCAL_JWT_STRAT } async validate(payload: ITokenPayload) { - return { id: payload.id, role: payload.role }; + return { + id: payload.id, + role: payload.role, + inboxId: payload.inboxId, + draftsId: payload.draftsId, + junkId: payload.junkId, + sentId: payload.sentId, + trashId: payload.trashId, + wildduckUserId: payload.wildduckUserId, + }; } } diff --git a/src/modules/email/DTO/email-params.dto.ts b/src/modules/email/DTO/email-params.dto.ts index 87050b9..411483d 100644 --- a/src/modules/email/DTO/email-params.dto.ts +++ b/src/modules/email/DTO/email-params.dto.ts @@ -1,6 +1,6 @@ import { ApiProperty } from "@nestjs/swagger"; import { Transform } from "class-transformer"; -import { IsNotEmpty, IsNumberString, IsString } from "class-validator"; +import { IsNotEmpty, IsNumber, IsNumberString, IsString } from "class-validator"; import { EmailMessage } from "../../../common/enums/message.enum"; @@ -42,6 +42,6 @@ export class UserMailboxParamDto { export class UserMailboxMessageParamDto { @ApiProperty({ description: "Message ID", example: "12345" }) @Transform(({ value }) => parseInt(value)) - @IsNumberString({ no_symbols: true }, { message: EmailMessage.MESSAGE_ID_MUST_BE_NUMBER }) + @IsNumber({}, { message: EmailMessage.MESSAGE_ID_MUST_BE_NUMBER }) messageId: number; } diff --git a/src/modules/email/email.controller.ts b/src/modules/email/email.controller.ts index 27eba10..147b00f 100644 --- a/src/modules/email/email.controller.ts +++ b/src/modules/email/email.controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Delete, Get, Param, Post, Query } from "@nestjs/common"; +import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from "@nestjs/common"; import { ApiOperation, ApiResponse } from "@nestjs/swagger"; import { QueueIdParamDto, UserMailboxMessageParamDto } from "./DTO/email-params.dto"; @@ -9,8 +9,8 @@ import { EmailService } from "./services/email.service"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; import { UserDec } from "../../common/decorators/user.decorator"; -@Controller("email") @AuthGuards() +@Controller("email") export class EmailController { constructor(private readonly emailService: EmailService) {} @@ -19,8 +19,8 @@ export class EmailController { @ApiResponse({ status: 201, description: "Email sent successfully" }) @ApiResponse({ status: 400, description: "Invalid email data" }) @ApiResponse({ status: 403, description: "Not authorized to send email" }) - sendEmail(@UserDec("id") userId: string, @Body() sendEmailDto: SendEmailDto) { - return this.emailService.sendEmail(userId, sendEmailDto); + sendEmail(@UserDec("wildduckUserId") userEmailId: string, @Body() sendEmailDto: SendEmailDto) { + return this.emailService.sendEmail(userEmailId, sendEmailDto); } @Get("status/:queueId") @@ -35,7 +35,7 @@ export class EmailController { @ApiOperation({ summary: "List messages in a inbox" }) @ApiResponse({ status: 200, description: "List of messages" }) @ApiResponse({ status: 404, description: "Mailbox not found" }) - listMessages(@UserDec("id") userId: string, @UserDec("inboxId") inboxId: string, @Query() query: MessageListQueryDto) { + listMessages(@UserDec("wildduckUserId") userId: string, @UserDec("inboxId") inboxId: string, @Query() query: MessageListQueryDto) { return this.emailService.listMessages(userId, inboxId, query); } @@ -43,7 +43,7 @@ export class EmailController { @ApiOperation({ summary: "Get a specific message" }) @ApiResponse({ status: 200, description: "Message details" }) @ApiResponse({ status: 404, description: "Message not found" }) - getMessage(@UserDec("id") userId: string, @UserDec("inboxId") inboxId: string, @Param() params: UserMailboxMessageParamDto) { + getMessage(@UserDec("wildduckUserId") userId: string, @UserDec("inboxId") inboxId: string, @Param() params: UserMailboxMessageParamDto) { return this.emailService.getMessage(userId, inboxId, params.messageId); } @@ -51,14 +51,38 @@ export class EmailController { @ApiOperation({ summary: "Delete a message" }) @ApiResponse({ status: 200, description: "Message deleted successfully" }) @ApiResponse({ status: 404, description: "Message not found" }) - deleteMessage(@UserDec("id") userId: string, @UserDec("inboxId") inboxId: string, @Param() params: UserMailboxMessageParamDto) { + deleteMessage(@UserDec("wildduckUserId") userId: string, @UserDec("inboxId") inboxId: string, @Param() params: UserMailboxMessageParamDto) { return this.emailService.deleteMessage(userId, inboxId, params.messageId); } @Get("search") @ApiOperation({ summary: "Search messages across all mailboxes" }) @ApiResponse({ status: 200, description: "Search results" }) - searchMessages(@UserDec("id") userId: string, @Query() query: SearchMessagesQueryDto) { + searchMessages(@UserDec("wildduckUserId") userId: string, @Query() query: SearchMessagesQueryDto) { return this.emailService.searchMessages(userId, query); } + + @Patch("messages/:messageId/seen") + @ApiOperation({ summary: "Mark a message as seen" }) + @ApiResponse({ status: 200, description: "Message marked as seen successfully" }) + @ApiResponse({ status: 404, description: "Message not found" }) + markMessageAsSeen(@UserDec("wildduckUserId") userId: string, @UserDec("inboxId") inboxId: string, @Param() params: UserMailboxMessageParamDto) { + return this.emailService.markMessageAsSeen(userId, inboxId, Number(params.messageId)); + } + + @Get("messages/sent") + @ApiOperation({ summary: "List messages in sent mailbox" }) + @ApiResponse({ status: 200, description: "List of sent messages" }) + @ApiResponse({ status: 404, description: "Sent mailbox not found" }) + getSentMessages(@UserDec("wildduckUserId") userId: string, @UserDec("sentId") sentId: string, @Query() query: MessageListQueryDto) { + return this.emailService.getSentMessages(userId, sentId, query); + } + + @Get("messages/trash") + @ApiOperation({ summary: "List messages in trash mailbox" }) + @ApiResponse({ status: 200, description: "List of trash messages" }) + @ApiResponse({ status: 404, description: "Trash mailbox not found" }) + getTrashMessages(@UserDec("wildduckUserId") userId: string, @UserDec("trashId") trashId: string, @Query() query: MessageListQueryDto) { + return this.emailService.getTrashMessages(userId, trashId, query); + } } diff --git a/src/modules/email/services/email.service.ts b/src/modules/email/services/email.service.ts index d22b222..7769dff 100644 --- a/src/modules/email/services/email.service.ts +++ b/src/modules/email/services/email.service.ts @@ -3,7 +3,6 @@ import { firstValueFrom } from "rxjs"; import { EmailMessage } from "../../../common/enums/message.enum"; import { MailServerService } from "../../mail-server/services/mail-server.service"; -import { UsersService } from "../../users/services/users.service"; import { MessageListQueryDto, SearchMessagesQueryDto } from "../DTO/email-query.dto"; import { SendEmailDto } from "../DTO/send-email.dto"; @@ -13,12 +12,12 @@ export class EmailService { constructor( private readonly mailServerService: MailServerService, - private readonly userService: UsersService, + // private readonly userService: UsersService, ) {} //######################################################## - async sendEmail(userId: string, sendEmailDto: SendEmailDto) { - const userEmailId = await this.userService.getUserEmailIdFromId(userId); + async sendEmail(userEmailId: string, sendEmailDto: SendEmailDto) { + // const userEmailId = await this.userService.getUserEmailIdFromId(userId); this.logger.log(`Sending email from user ${userEmailId} to ${sendEmailDto.to.map((t) => t.address).join(", ")}`); @@ -87,13 +86,15 @@ export class EmailService { this.logger.log(`Listing messages in mailbox ${inboxId} for user: ${userId}`); try { - const response = await firstValueFrom(this.mailServerService.messages.listMessages(userId, inboxId, query)); + const { results, total: count } = await firstValueFrom(this.mailServerService.messages.listMessages(userId, inboxId, query)); - this.logger.log(`Found ${response.results.length} messages in mailbox ${inboxId} for user: ${userId}`); + this.logger.log(`Found ${count} messages in mailbox ${inboxId} for user: ${userId}`); return { message: EmailMessage.MESSAGES_LISTED_SUCCESSFULLY, - ...response, + results, + count, + paginate: true, }; } catch (error) { this.logger.error( @@ -104,17 +105,17 @@ export class EmailService { } //######################################################## - async getMessage(userId: string, mailboxId: string, messageId: number) { - this.logger.log(`Getting message ${messageId} from mailbox ${mailboxId} for user: ${userId}`); + async getMessage(userId: string, inboxId: string, messageId: number) { + this.logger.log(`Getting message ${messageId} from inbox ${inboxId} for user: ${userId}`); try { - const response = await firstValueFrom(this.mailServerService.messages.getMessage(userId, mailboxId, messageId)); + await this.markMessageAsSeen(userId, inboxId, messageId); + const message = await firstValueFrom(this.mailServerService.messages.getMessage(userId, inboxId, messageId)); this.logger.log(`Retrieved message ${messageId} for user: ${userId}`); return { - message: EmailMessage.MESSAGE_RETRIEVED_SUCCESSFULLY, - ...response, + message, }; } catch (error) { this.logger.error(`Failed to get message ${messageId} for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`); @@ -123,21 +124,68 @@ export class EmailService { } //######################################################## - async deleteMessage(userId: string, mailboxId: string, messageId: number) { - this.logger.log(`Deleting message ${messageId} from mailbox ${mailboxId} for user: ${userId}`); - + async deleteMessage(userId: string, inboxId: string, messageId: number) { try { - const response = await firstValueFrom(this.mailServerService.messages.deleteMessage(userId, mailboxId, messageId)); - - this.logger.log(`Deleted message ${messageId} for user: ${userId}`); + const result = await firstValueFrom(this.mailServerService.messages.deleteMessage(userId, inboxId, messageId)); return { + success: true, message: EmailMessage.MESSAGE_DELETED_SUCCESSFULLY, - ...response, + result, }; } catch (error) { this.logger.error(`Failed to delete message ${messageId} for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`); throw error; } } + + //============================================== + async markMessageAsSeen(userId: string, inboxId: string, messageId: number) { + try { + const result = await firstValueFrom(this.mailServerService.messages.updateMessage(userId, inboxId, messageId, { seen: true })); + + return { + success: true, + message: EmailMessage.MESSAGE_MARKED_AS_SEEN_SUCCESSFULLY, + data: result, + }; + } catch (error) { + this.logger.error( + `Failed to mark message ${messageId} as seen for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`, + ); + throw error; + } + } + + //============================================== + async getSentMessages(userId: string, sentId: string, query: MessageListQueryDto) { + try { + const result = await firstValueFrom(this.mailServerService.messages.listMessages(userId, sentId, query)); + + return { + success: true, + message: EmailMessage.SENT_MESSAGES_RETRIEVED_SUCCESSFULLY, + data: result, + }; + } catch (error) { + this.logger.error(`Failed to get sent messages for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`); + throw error; + } + } + + //============================================== + async getTrashMessages(userId: string, trashId: string, query: MessageListQueryDto) { + try { + const result = await firstValueFrom(this.mailServerService.messages.listMessages(userId, trashId, query)); + + return { + success: true, + message: EmailMessage.TRASH_MESSAGES_RETRIEVED_SUCCESSFULLY, + data: result, + }; + } catch (error) { + this.logger.error(`Failed to get trash messages for user ${userId}: ${error instanceof Error ? error.message : "Unknown error"}`); + throw error; + } + } }