From 60d29f8e93510a215b6412bcf4459e573be906b1 Mon Sep 17 00:00:00 2001
From: mahyargdz
Date: Mon, 14 Jul 2025 16:59:23 +0330
Subject: [PATCH] chore: template phase 2
---
src/modules/email/email.module.ts | 5 +-
src/modules/email/services/email.service.ts | 22 ++-
.../interfaces/structure.interface.ts | 12 ++
.../services/template-processor.service.ts | 154 ++++--------------
src/modules/templates/templates.controller.ts | 2 +-
5 files changed, 67 insertions(+), 128 deletions(-)
diff --git a/src/modules/email/email.module.ts b/src/modules/email/email.module.ts
index cf7728e..e971ed3 100644
--- a/src/modules/email/email.module.ts
+++ b/src/modules/email/email.module.ts
@@ -10,7 +10,6 @@ import { MailboxResolverService } from "./services/mailbox-resolver.service";
import { WebSocketAuthService } from "./services/websocket-auth.service";
import { jwtConfig } from "../../configs/jwt.config";
import { MailServerModule } from "../mail-server/mail-server.module";
-import { TemplateProcessorService } from "../templates/services/template-processor.service";
import { TemplatesModule } from "../templates/templates.module";
import { User } from "../users/entities/user.entity";
import { UsersModule } from "../users/users.module";
@@ -18,7 +17,7 @@ import { UsersModule } from "../users/users.module";
@Module({
imports: [MailServerModule, UsersModule, TemplatesModule, JwtModule.registerAsync(jwtConfig()), MikroOrmModule.forFeature([User])],
controllers: [EmailController],
- providers: [EmailService, MailboxResolverService, EmailGateway, EmailNotificationService, WebSocketAuthService, TemplateProcessorService],
- exports: [EmailService, MailboxResolverService, EmailGateway, EmailNotificationService, WebSocketAuthService, TemplateProcessorService],
+ providers: [EmailService, MailboxResolverService, EmailGateway, EmailNotificationService, WebSocketAuthService],
+ exports: [EmailService, MailboxResolverService, EmailGateway, EmailNotificationService, WebSocketAuthService],
})
export class EmailModule {}
diff --git a/src/modules/email/services/email.service.ts b/src/modules/email/services/email.service.ts
index 70fb0da..d63b3ac 100644
--- a/src/modules/email/services/email.service.ts
+++ b/src/modules/email/services/email.service.ts
@@ -37,15 +37,20 @@ export class EmailService {
this.logger.log(`Processing email through business template for business: ${user.business.id}`);
// Process email content through business template
- const processedTemplate = await this.templateProcessorService.processEmailWithTemplate(user.business.id, {
+ const processedTemplate = await this.templateProcessorService.processEmailContent(user.business.id, {
text: sendEmailDto.text,
html: sendEmailDto.html,
subject: sendEmailDto.subject,
});
// Update the DTO with processed content
- sendEmailDto.html = processedTemplate.html;
- sendEmailDto.text = processedTemplate.text;
+ if (processedTemplate.isHtml) {
+ sendEmailDto.html = processedTemplate.content;
+ sendEmailDto.text = undefined; // Clear text when using HTML template
+ } else {
+ sendEmailDto.text = processedTemplate.content;
+ sendEmailDto.html = undefined; // Clear HTML when no template
+ }
if (processedTemplate.hasTemplate) {
this.logger.log(`Email content processed through template for business: ${user.business.id}`);
@@ -410,14 +415,19 @@ export class EmailService {
const user = await this.userRepository.findOne({ wildduckUserId: userEmailId, deletedAt: null }, { populate: ["business"] });
if (user && user.business && (updateDraftDto.text || updateDraftDto.html)) {
- const processedTemplate = await this.templateProcessorService.processEmailWithTemplate(user.business.id, {
+ const processedTemplate = await this.templateProcessorService.processEmailContent(user.business.id, {
text: updateDraftDto.text,
html: updateDraftDto.html,
subject: updateDraftDto.subject,
});
- draftData.html = processedTemplate.html;
- draftData.text = processedTemplate.text;
+ if (processedTemplate.isHtml) {
+ draftData.html = processedTemplate.content;
+ draftData.text = undefined;
+ } else {
+ draftData.text = processedTemplate.content;
+ draftData.html = undefined;
+ }
if (processedTemplate.hasTemplate) {
this.logger.log(`Draft content processed through template for business: ${user.business.id}`);
diff --git a/src/modules/templates/interfaces/structure.interface.ts b/src/modules/templates/interfaces/structure.interface.ts
index 36c1486..3cae048 100644
--- a/src/modules/templates/interfaces/structure.interface.ts
+++ b/src/modules/templates/interfaces/structure.interface.ts
@@ -102,3 +102,15 @@ export type ImageType = {
alignment?: HorizontalAlignment;
verticalAlignment?: VerticalAlignment;
};
+
+export interface EmailContent {
+ text?: string;
+ html?: string;
+ subject?: string;
+}
+
+export interface ProcessedEmailResult {
+ content: string;
+ isHtml: boolean;
+ hasTemplate: boolean;
+}
diff --git a/src/modules/templates/services/template-processor.service.ts b/src/modules/templates/services/template-processor.service.ts
index 20e4a8a..8a9c93e 100644
--- a/src/modules/templates/services/template-processor.service.ts
+++ b/src/modules/templates/services/template-processor.service.ts
@@ -1,13 +1,7 @@
import { Injectable, Logger } from "@nestjs/common";
import { TemplatesService } from "./templates.service";
-// import { PersonalityDataType, SectionItemType, TextType } from "../interfaces/structure.interface";
-
-interface ProcessedTemplateResult {
- html: string;
- text: string;
- hasTemplate: boolean;
-}
+import { EmailContent, ProcessedEmailResult } from "../interfaces/structure.interface";
@Injectable()
export class TemplateProcessorService {
@@ -15,101 +9,66 @@ export class TemplateProcessorService {
constructor(private readonly templatesService: TemplatesService) {}
- /**
- * Process email content through business template
- */
- async processEmailWithTemplate(
- businessId: string,
- emailContent: { text?: string; html?: string; subject?: string },
- // userId?: string,
- ): Promise {
+ //**************************************************** */
+ async processEmailContent(businessId: string, emailContent: EmailContent): Promise {
try {
- this.logger.log(`Processing email with template for business: ${businessId}`);
+ // Get selected template for the business
+ const selectedTemplate = await this.getSelectedTemplate(businessId);
- // Get business templates
- const templates = await this.templatesService.getTemplatesByBusinessId(businessId);
-
- if (!templates || templates.length === 0) {
- this.logger.log(`No templates found for business: ${businessId}, using original content`);
+ if (!selectedTemplate) {
+ // No template - return original text content
return {
- html: emailContent.html || this.convertTextToHtml(emailContent.text || ""),
- text: emailContent.text || this.stripHtmlTags(emailContent.html || ""),
+ content: emailContent.text || emailContent.html || "",
+ isHtml: false,
hasTemplate: false,
};
}
- // Use the first template
- const template = templates[0];
-
- this.logger.log(`Using template: ${template.name} for email processing`);
-
- // Process the template with email content
- const processedContent = await this.injectContentIntoTemplate(emailContent, template.rawHtml);
+ // Process with template
+ const processedContent = this.injectContentIntoTemplate(selectedTemplate.rawHtml, emailContent);
return {
- html: processedContent.html,
- text: processedContent.text,
+ content: processedContent,
+ isHtml: true,
hasTemplate: true,
};
} catch (error) {
- this.logger.error(`Failed to process email with template for business ${businessId}:`, error);
+ this.logger.error(`Failed to process email for business ${businessId}:`, error);
- // Fallback to original content if template processing fails
+ // Fallback to original content
return {
- html: emailContent.html || this.convertTextToHtml(emailContent.text || ""),
- text: emailContent.text || this.stripHtmlTags(emailContent.html || ""),
+ content: emailContent.text || emailContent.html || "",
+ isHtml: false,
hasTemplate: false,
};
}
}
- /**
- * Inject email content into template structure
- */
- private async injectContentIntoTemplate(
- // templateStructure: Record,
- emailContent: { text?: string; html?: string; subject?: string },
- rawHtml?: string,
- ): Promise<{ html: string; text: string }> {
- try {
- // If template has raw HTML, use it as base and inject content
- if (rawHtml) {
- const html = this.injectContentIntoRawHtml(rawHtml, emailContent);
- return {
- html,
- text: this.stripHtmlTags(html),
- };
- }
-
- // Otherwise, build simple HTML with email content
- const html = this.buildSimpleTemplate(emailContent);
- return {
- html,
- text: this.stripHtmlTags(html),
- };
- } catch (error) {
- this.logger.error("Failed to inject content into template:", error);
- throw error;
- }
+ //**************************************************** */
+ private async getSelectedTemplate(businessId: string) {
+ const templates = await this.templatesService.getTemplatesByBusinessId(businessId);
+ return templates.find((template) => template.selected) || null;
}
- /**
- * Inject email content into raw HTML template
- */
- private injectContentIntoRawHtml(rawHtml: string, emailContent: { text?: string; html?: string; subject?: string }): string {
- let processedHtml = rawHtml;
+ //**************************************************** */
+ private injectContentIntoTemplate(templateHtml: string | undefined, emailContent: EmailContent): string {
+ if (!templateHtml) {
+ return emailContent.text || emailContent.html || "";
+ }
- // Replace placeholders with actual content
- const contentToInject = emailContent.html || this.convertTextToHtml(emailContent.text || "");
+ let processedHtml = templateHtml;
- // Common placeholder patterns
- const placeholders = [/\{\{content\}\}/gi, /\{\{message\}\}/gi, /\{\{body\}\}/gi, /\{content\}/gi, /\{message\}/gi, /\{body\}/gi];
+ // Content to inject (prefer text, fallback to html)
+ const contentToInject = emailContent.text || this.stripHtmlTags(emailContent.html || "");
- placeholders.forEach((placeholder) => {
+ // 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
+ // Replace subject placeholders if subject exists
if (emailContent.subject) {
const subjectPlaceholders = [/\{\{subject\}\}/gi, /\{\{title\}\}/gi, /\{subject\}/gi, /\{title\}/gi];
@@ -121,49 +80,7 @@ export class TemplateProcessorService {
return processedHtml;
}
- /**
- * Build simple HTML template
- */
- private buildSimpleTemplate(emailContent: { text?: string; html?: string; subject?: string }): string {
- const content = emailContent.html || this.convertTextToHtml(emailContent.text || "");
-
- return `
-
-
-
-
-
- ${emailContent.subject || "Email"}
-
-
-
-
-
-
- `;
- }
-
- /**
- * Convert plain text to HTML
- */
- private convertTextToHtml(text: string): string {
- return text
- .replace(/\n\n/g, "
")
- .replace(/\n/g, "
")
- .replace(/^(.*)$/, "
$1
");
- }
-
- /**
- * Strip HTML tags from content
- */
+ //**************************************************** */
private stripHtmlTags(html: string): string {
return html
.replace(/<[^>]*>/g, "")
@@ -172,6 +89,7 @@ export class TemplateProcessorService {
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, '"')
+ .replace(/'/g, "'")
.trim();
}
}
diff --git a/src/modules/templates/templates.controller.ts b/src/modules/templates/templates.controller.ts
index 4baaac5..9e3b0cf 100644
--- a/src/modules/templates/templates.controller.ts
+++ b/src/modules/templates/templates.controller.ts
@@ -57,7 +57,7 @@ export class TemplatesController {
return this.templatesService.deleteTemplate(params.id, businessId);
}
- @Post(":id/set-selected")
+ @Patch(":id/set-selected")
@ApiOperation({ summary: "Set template as selected (unselects all other templates for the business)" })
@ApiResponse({ status: 200, description: "Template set as selected successfully" })
@ApiResponse({ status: 404, description: "Template not found" })