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