chore: template phase 2

This commit is contained in:
mahyargdz
2025-07-14 16:59:23 +03:30
parent 513980fe3a
commit 60d29f8e93
5 changed files with 67 additions and 128 deletions
+2 -3
View File
@@ -10,7 +10,6 @@ import { MailboxResolverService } from "./services/mailbox-resolver.service";
import { WebSocketAuthService } from "./services/websocket-auth.service"; import { WebSocketAuthService } from "./services/websocket-auth.service";
import { jwtConfig } from "../../configs/jwt.config"; import { jwtConfig } from "../../configs/jwt.config";
import { MailServerModule } from "../mail-server/mail-server.module"; import { MailServerModule } from "../mail-server/mail-server.module";
import { TemplateProcessorService } from "../templates/services/template-processor.service";
import { TemplatesModule } from "../templates/templates.module"; import { TemplatesModule } from "../templates/templates.module";
import { User } from "../users/entities/user.entity"; import { User } from "../users/entities/user.entity";
import { UsersModule } from "../users/users.module"; import { UsersModule } from "../users/users.module";
@@ -18,7 +17,7 @@ import { UsersModule } from "../users/users.module";
@Module({ @Module({
imports: [MailServerModule, UsersModule, TemplatesModule, JwtModule.registerAsync(jwtConfig()), MikroOrmModule.forFeature([User])], imports: [MailServerModule, UsersModule, TemplatesModule, JwtModule.registerAsync(jwtConfig()), MikroOrmModule.forFeature([User])],
controllers: [EmailController], controllers: [EmailController],
providers: [EmailService, MailboxResolverService, EmailGateway, EmailNotificationService, WebSocketAuthService, TemplateProcessorService], providers: [EmailService, MailboxResolverService, EmailGateway, EmailNotificationService, WebSocketAuthService],
exports: [EmailService, MailboxResolverService, EmailGateway, EmailNotificationService, WebSocketAuthService, TemplateProcessorService], exports: [EmailService, MailboxResolverService, EmailGateway, EmailNotificationService, WebSocketAuthService],
}) })
export class EmailModule {} export class EmailModule {}
+16 -6
View File
@@ -37,15 +37,20 @@ export class EmailService {
this.logger.log(`Processing email through business template for business: ${user.business.id}`); this.logger.log(`Processing email through business template for business: ${user.business.id}`);
// Process email content through business template // 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, text: sendEmailDto.text,
html: sendEmailDto.html, html: sendEmailDto.html,
subject: sendEmailDto.subject, subject: sendEmailDto.subject,
}); });
// Update the DTO with processed content // Update the DTO with processed content
sendEmailDto.html = processedTemplate.html; if (processedTemplate.isHtml) {
sendEmailDto.text = processedTemplate.text; 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) { if (processedTemplate.hasTemplate) {
this.logger.log(`Email content processed through template for business: ${user.business.id}`); 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"] }); const user = await this.userRepository.findOne({ wildduckUserId: userEmailId, deletedAt: null }, { populate: ["business"] });
if (user && user.business && (updateDraftDto.text || updateDraftDto.html)) { 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, text: updateDraftDto.text,
html: updateDraftDto.html, html: updateDraftDto.html,
subject: updateDraftDto.subject, subject: updateDraftDto.subject,
}); });
draftData.html = processedTemplate.html; if (processedTemplate.isHtml) {
draftData.text = processedTemplate.text; draftData.html = processedTemplate.content;
draftData.text = undefined;
} else {
draftData.text = processedTemplate.content;
draftData.html = undefined;
}
if (processedTemplate.hasTemplate) { if (processedTemplate.hasTemplate) {
this.logger.log(`Draft content processed through template for business: ${user.business.id}`); this.logger.log(`Draft content processed through template for business: ${user.business.id}`);
@@ -102,3 +102,15 @@ export type ImageType = {
alignment?: HorizontalAlignment; alignment?: HorizontalAlignment;
verticalAlignment?: VerticalAlignment; verticalAlignment?: VerticalAlignment;
}; };
export interface EmailContent {
text?: string;
html?: string;
subject?: string;
}
export interface ProcessedEmailResult {
content: string;
isHtml: boolean;
hasTemplate: boolean;
}
@@ -1,13 +1,7 @@
import { Injectable, Logger } from "@nestjs/common"; import { Injectable, Logger } from "@nestjs/common";
import { TemplatesService } from "./templates.service"; import { TemplatesService } from "./templates.service";
// import { PersonalityDataType, SectionItemType, TextType } from "../interfaces/structure.interface"; import { EmailContent, ProcessedEmailResult } from "../interfaces/structure.interface";
interface ProcessedTemplateResult {
html: string;
text: string;
hasTemplate: boolean;
}
@Injectable() @Injectable()
export class TemplateProcessorService { export class TemplateProcessorService {
@@ -15,101 +9,66 @@ export class TemplateProcessorService {
constructor(private readonly templatesService: TemplatesService) {} constructor(private readonly templatesService: TemplatesService) {}
/** //**************************************************** */
* Process email content through business template async processEmailContent(businessId: string, emailContent: EmailContent): Promise<ProcessedEmailResult> {
*/
async processEmailWithTemplate(
businessId: string,
emailContent: { text?: string; html?: string; subject?: string },
// userId?: string,
): Promise<ProcessedTemplateResult> {
try { 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 if (!selectedTemplate) {
const templates = await this.templatesService.getTemplatesByBusinessId(businessId); // No template - return original text content
if (!templates || templates.length === 0) {
this.logger.log(`No templates found for business: ${businessId}, using original content`);
return { return {
html: emailContent.html || this.convertTextToHtml(emailContent.text || ""), content: emailContent.text || emailContent.html || "",
text: emailContent.text || this.stripHtmlTags(emailContent.html || ""), isHtml: false,
hasTemplate: false, hasTemplate: false,
}; };
} }
// Use the first template // Process with template
const template = templates[0]; const processedContent = this.injectContentIntoTemplate(selectedTemplate.rawHtml, emailContent);
this.logger.log(`Using template: ${template.name} for email processing`);
// Process the template with email content
const processedContent = await this.injectContentIntoTemplate(emailContent, template.rawHtml);
return { return {
html: processedContent.html, content: processedContent,
text: processedContent.text, isHtml: true,
hasTemplate: true, hasTemplate: true,
}; };
} catch (error) { } 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 { return {
html: emailContent.html || this.convertTextToHtml(emailContent.text || ""), content: emailContent.text || emailContent.html || "",
text: emailContent.text || this.stripHtmlTags(emailContent.html || ""), isHtml: false,
hasTemplate: false, hasTemplate: false,
}; };
} }
} }
/** //**************************************************** */
* Inject email content into template structure private async getSelectedTemplate(businessId: string) {
*/ const templates = await this.templatesService.getTemplatesByBusinessId(businessId);
private async injectContentIntoTemplate( return templates.find((template) => template.selected) || null;
// templateStructure: Record<string, unknown>,
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); private injectContentIntoTemplate(templateHtml: string | undefined, emailContent: EmailContent): string {
return { if (!templateHtml) {
html, return emailContent.text || emailContent.html || "";
text: this.stripHtmlTags(html),
};
} catch (error) {
this.logger.error("Failed to inject content into template:", error);
throw error;
}
} }
/** let processedHtml = templateHtml;
* Inject email content into raw HTML template
*/
private injectContentIntoRawHtml(rawHtml: string, emailContent: { text?: string; html?: string; subject?: string }): string {
let processedHtml = rawHtml;
// Replace placeholders with actual content // Content to inject (prefer text, fallback to html)
const contentToInject = emailContent.html || this.convertTextToHtml(emailContent.text || ""); const contentToInject = emailContent.text || this.stripHtmlTags(emailContent.html || "");
// Common placeholder patterns // Replace content placeholders
const placeholders = [/\{\{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];
placeholders.forEach((placeholder) => { contentPlaceholders.forEach((placeholder) => {
processedHtml = processedHtml.replace(placeholder, contentToInject); processedHtml = processedHtml.replace(placeholder, contentToInject);
}); });
// Replace subject placeholders // 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];
@@ -121,49 +80,7 @@ export class TemplateProcessorService {
return processedHtml; 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 `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${emailContent.subject || "Email"}</title>
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; line-height: 1.6; }
.email-container { max-width: 600px; margin: 0 auto; }
.content { padding: 20px; }
</style>
</head>
<body>
<div class="email-container">
<div class="content">
${content}
</div>
</div>
</body>
</html>
`;
}
/**
* Convert plain text to HTML
*/
private convertTextToHtml(text: string): string {
return text
.replace(/\n\n/g, "</p><p>")
.replace(/\n/g, "<br>")
.replace(/^(.*)$/, "<p>$1</p>");
}
/**
* Strip HTML tags from content
*/
private stripHtmlTags(html: string): string { private stripHtmlTags(html: string): string {
return html return html
.replace(/<[^>]*>/g, "") .replace(/<[^>]*>/g, "")
@@ -172,6 +89,7 @@ export class TemplateProcessorService {
.replace(/&lt;/g, "<") .replace(/&lt;/g, "<")
.replace(/&gt;/g, ">") .replace(/&gt;/g, ">")
.replace(/&quot;/g, '"') .replace(/&quot;/g, '"')
.replace(/&#39;/g, "'")
.trim(); .trim();
} }
} }
@@ -57,7 +57,7 @@ export class TemplatesController {
return this.templatesService.deleteTemplate(params.id, businessId); 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)" }) @ApiOperation({ summary: "Set template as selected (unselects all other templates for the business)" })
@ApiResponse({ status: 200, description: "Template set as selected successfully" }) @ApiResponse({ status: 200, description: "Template set as selected successfully" })
@ApiResponse({ status: 404, description: "Template not found" }) @ApiResponse({ status: 404, description: "Template not found" })