chore: update the template processor

This commit is contained in:
mahyargdz
2025-08-04 12:14:30 +03:30
parent 6c1904cb60
commit c21037a683
10 changed files with 61 additions and 43 deletions
-1
View File
@@ -92,7 +92,6 @@
"@types/nodemailer": "^6.4.17",
"@types/passport-jwt": "^4.0.1",
"@types/supertest": "^6.0.3",
"@types/web-push": "^3.6.4",
"@typescript-eslint/eslint-plugin": "^8.35.1",
"@typescript-eslint/parser": "^8.35.1",
"eslint": "^9.30.1",
+2
View File
@@ -281,6 +281,8 @@ export const enum EmailMessage {
TRASH_EMPTYED_SUCCESSFULLY = "سطل زباله با موفقیت خالی شد",
SPAM_EMPTYED_SUCCESSFULLY = "همه پیام های اسپم با موفقیت حذف شدند",
SAVE_DRAFT_SUCCESS = "پیش نویس با موفقیت ذخیره شد",
HTML_CONTENT_REQUIRED = "محتوای HTML مورد نیاز است",
TEXT_CONTENT_REQUIRED = "TEXT_CONTENT_REQUIRED",
}
export const enum WebSocketMessage {
@@ -76,7 +76,6 @@ export class EmailPollingProcessor extends WorkerProcessor {
timestamp: email.date || new Date().toISOString(),
mailboxId: email.mailbox,
mailboxName: "Inbox",
isRead: !!email.seen,
hasAttachments: !!(email.attachments && Array.isArray(email.attachments) && email.attachments.length > 0),
});
@@ -80,6 +80,16 @@ export class EmailNotificationService {
}
}
//************************************************************************ */
async notifyEmailSentFromDraft(emailSentFromDraftData: IEmailSentPayload) {
try {
await this.emailGateway.notifyEmailSent(emailSentFromDraftData.userId, emailSentFromDraftData);
await this.updateUnreadCount(emailSentFromDraftData.wildduckUserId, emailSentFromDraftData.userId);
} catch (error) {
this.logger.error(`Failed to notify email sent from draft for user ${emailSentFromDraftData.userId}:`, error);
}
}
//************************************************************************ */
async notifyEmailDeleted(emailDeletedData: IEmailDeletePayload) {
try {
+4 -4
View File
@@ -157,15 +157,15 @@ export class SendEmailDto {
@ApiPropertyOptional({ description: "Message subject. If not then resolved from Reference message (optional)", example: "Test email" })
subject: string;
@IsOptional()
@IsNotEmpty({ message: EmailMessage.TEXT_CONTENT_REQUIRED })
@IsString({ message: EmailMessage.TEXT_CONTENT_MUST_BE_STRING })
@ApiPropertyOptional({ description: "Plaintext message (optional)" })
text?: string;
text: string;
@IsOptional()
@IsNotEmpty({ message: EmailMessage.HTML_CONTENT_REQUIRED })
@IsString({ message: EmailMessage.HTML_CONTENT_MUST_BE_STRING })
@ApiPropertyOptional({ description: "HTML formatted message (optional)" })
html?: string;
html: string;
@IsOptional()
@IsArray({ message: EmailMessage.ATTACHMENTS_MUST_BE_ARRAY })
+2 -2
View File
@@ -207,8 +207,8 @@ export class EmailController {
@ApiResponse({ status: 200, description: "Draft sent successfully" })
@ApiResponse({ status: 404, description: "Draft not found" })
@ApiResponse({ status: 403, description: "Not authorized to send draft" })
sendDraft(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto) {
return this.emailService.sendDraft(userId, Number(params.messageId));
sendDraft(@UserDec() user: User, @Param() params: MessageParamDto) {
return this.emailService.sendDraft(user.wildduckUserId, user.id, Number(params.messageId));
}
@Delete("messages/drafts/:messageId")
@@ -2,6 +2,8 @@
// BASE INTERFACES
// ====================
import { MessageAddress } from "../../mail-server/interfaces/messages-response.interface";
export interface IBaseEventPayload {
userId: string;
timestamp: string;
@@ -23,21 +25,15 @@ export interface IBaseMessageMailboxEventPayload extends IBaseMessageEventPayloa
// COMMON EMAIL INTERFACES
// ====================
export interface IEmailContact {
name?: string;
address: string;
}
export interface IEmailMetadata {
subject: string;
hasAttachments: boolean;
mailboxName: string;
isRead: boolean;
}
export interface IEmailContent extends IEmailMetadata {
from: IEmailContact;
to: IEmailContact[];
from: MessageAddress;
to: MessageAddress[];
preview?: string;
}
+20 -6
View File
@@ -58,10 +58,10 @@ export class EmailService {
if (processedTemplate.isHtml) {
sendEmailDto.html = processedTemplate.content;
sendEmailDto.text = undefined;
sendEmailDto.text = "";
} else {
sendEmailDto.text = processedTemplate.content;
sendEmailDto.html = undefined;
sendEmailDto.html = "";
}
}
@@ -86,7 +86,6 @@ export class EmailService {
from: sendEmailDto.from,
hasAttachments: false,
mailboxName: "Sent",
isRead: true,
timestamp: new Date().toISOString(),
});
@@ -514,10 +513,10 @@ export class EmailService {
if (processedTemplate.isHtml) {
draftData.html = processedTemplate.content;
draftData.text = undefined;
draftData.text = "";
} else {
draftData.text = processedTemplate.content;
draftData.html = undefined;
draftData.html = "";
}
const headers = this.generateHeadersForEmailType(user, {
@@ -538,12 +537,27 @@ export class EmailService {
}
//==============================================
async sendDraft(wildduckUserId: string, messageId: number) {
async sendDraft(wildduckUserId: string, userId: string, messageId: number) {
const draftsMailboxId = await this.mailboxResolverService.getDraftsMailboxId(wildduckUserId);
const result = await firstValueFrom(
this.mailServerService.messages.submitDraft(wildduckUserId, draftsMailboxId, messageId, { deleteFiles: false }),
);
const message = await firstValueFrom(this.mailServerService.messages.getMessage(wildduckUserId, draftsMailboxId, messageId));
await this.emailNotificationService.notifyEmailSentFromDraft({
wildduckUserId,
userId,
from: message.from,
to: message.to!,
subject: message.subject,
hasAttachments: false,
mailboxName: message.mailbox,
messageId: Number(message.id),
mailboxId: message.mailbox,
timestamp: new Date().toISOString(),
});
return {
message: EmailMessage.DRAFT_SENT_SUCCESSFULLY,
messageId: result.message.id,
@@ -180,7 +180,7 @@ export type PersonalityDataType = {
};
export interface EmailContent {
text?: string;
html?: string;
text: string;
html: string;
subject?: string;
}
@@ -18,7 +18,7 @@ export class TemplateProcessorService {
if (!selectedTemplate) {
return {
content: emailContent.text || emailContent.html || "",
content: emailContent.html || emailContent.text || "",
isHtml: false,
hasTemplate: false,
};
@@ -45,23 +45,21 @@ export class TemplateProcessorService {
//**************************************************** */
private async injectContentIntoTemplate(templateHtml: string | undefined, emailContent: EmailContent) {
if (!templateHtml) {
return emailContent.text || emailContent.html || "";
}
const { html, text } = emailContent;
if (!templateHtml) return html || text || "";
let processedHtml = templateHtml;
// Content to inject (prefer text, fallback to html)
const contentToInject = emailContent.text || this.stripHtmlTags(emailContent.html || "");
// const contentToInject = this.stripHtmlTags(html || text);
const contentToInject = html || text;
// Replace content placeholders
const contentPlaceholders = [/\{\{content\}\}/gi, /\{\{message\}\}/gi, /\{\{body\}\}/gi, /\{content\}/gi, /\{message\}/gi, /\{body\}/gi];
contentPlaceholders.forEach((placeholder) => {
processedHtml = processedHtml.replace(placeholder, contentToInject);
});
// Replace subject placeholders if subject exists
if (emailContent.subject) {
const subjectPlaceholders = [/\{\{subject\}\}/gi, /\{\{title\}\}/gi, /\{subject\}/gi, /\{title\}/gi];
@@ -74,15 +72,15 @@ export class TemplateProcessorService {
}
//**************************************************** */
private stripHtmlTags(html: string): string {
return html
.replace(/<[^>]*>/g, "")
.replace(/&nbsp;/g, " ")
.replace(/&amp;/g, "&")
.replace(/&lt;/g, "<")
.replace(/&gt;/g, ">")
.replace(/&quot;/g, '"')
.replace(/&#39;/g, "'")
.trim();
}
// private stripHtmlTags(html: string): string {
// return html
// .replace(/<[^>]*>/g, "")
// .replace(/&nbsp;/g, " ")
// .replace(/&amp;/g, "&")
// .replace(/&lt;/g, "<")
// .replace(/&gt;/g, ">")
// .replace(/&quot;/g, '"')
// .replace(/&#39;/g, "'")
// .trim();
// }
}