chore: template
This commit is contained in:
@@ -22,6 +22,7 @@ import { MailServerModule } from "./modules/mail-server/mail-server.module";
|
|||||||
import { MailboxModule } from "./modules/mailbox/mailbox.module";
|
import { MailboxModule } from "./modules/mailbox/mailbox.module";
|
||||||
import { QuotaSyncModule } from "./modules/quota-sync/quota-sync.module";
|
import { QuotaSyncModule } from "./modules/quota-sync/quota-sync.module";
|
||||||
import { EmailSignaturesModule } from "./modules/signatures/email-signatures.module";
|
import { EmailSignaturesModule } from "./modules/signatures/email-signatures.module";
|
||||||
|
import { TemplatesModule } from "./modules/templates/templates.module";
|
||||||
import { UsersModule } from "./modules/users/users.module";
|
import { UsersModule } from "./modules/users/users.module";
|
||||||
import { UtilsModule } from "./modules/utils/utils.module";
|
import { UtilsModule } from "./modules/utils/utils.module";
|
||||||
|
|
||||||
@@ -44,6 +45,7 @@ import { UtilsModule } from "./modules/utils/utils.module";
|
|||||||
MailServerModule,
|
MailServerModule,
|
||||||
QuotaSyncModule,
|
QuotaSyncModule,
|
||||||
EmailSignaturesModule,
|
EmailSignaturesModule,
|
||||||
|
TemplatesModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AppModule implements NestModule {
|
export class AppModule implements NestModule {
|
||||||
|
|||||||
@@ -449,3 +449,26 @@ export const enum SignatureMessage {
|
|||||||
DELETED = "امضاء با موفقیت حذف شد",
|
DELETED = "امضاء با موفقیت حذف شد",
|
||||||
TOGGLE_ACTIVE = "وضعیت امضاء با موفقیت تغییر کرد",
|
TOGGLE_ACTIVE = "وضعیت امضاء با موفقیت تغییر کرد",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const enum TemplateMessage {
|
||||||
|
TEMPLATE_NOT_FOUND = "قالب یافت نشد",
|
||||||
|
TEMPLATE_CREATED_SUCCESSFULLY = "قالب با موفقیت ایجاد شد",
|
||||||
|
TEMPLATE_UPDATED_SUCCESSFULLY = "قالب با موفقیت به روزرسانی شد",
|
||||||
|
TEMPLATE_DELETED_SUCCESSFULLY = "قالب با موفقیت حذف شد",
|
||||||
|
TEMPLATE_RETRIEVED_SUCCESSFULLY = "قالب با موفقیت دریافت شد",
|
||||||
|
TEMPLATES_LISTED_SUCCESSFULLY = "لیست قالب ها با موفقیت دریافت شد",
|
||||||
|
TEMPLATE_NAME_REQUIRED = "نام قالب الزامی است",
|
||||||
|
TEMPLATE_NAME_STRING = "نام قالب باید یک رشته باشد",
|
||||||
|
TEMPLATE_NAME_MAX_LENGTH = "نام قالب نمیتواند بیشتر از ۲۵۵ کاراکتر باشد",
|
||||||
|
TEMPLATE_STRUCTURE_REQUIRED = "ساختار قالب الزامی است",
|
||||||
|
TEMPLATE_STRUCTURE_VALID = "ساختار قالب باید معتبر باشد",
|
||||||
|
TEMPLATE_RAW_HTML_STRING = "HTML خام باید یک رشته باشد",
|
||||||
|
TEMPLATE_BUSINESS_ID_REQUIRED = "شناسه کسب و کار الزامی است",
|
||||||
|
TEMPLATE_BUSINESS_ID_UUID = "شناسه کسب و کار باید یک UUID معتبر باشد",
|
||||||
|
TEMPLATE_ID_REQUIRED = "شناسه قالب الزامی است",
|
||||||
|
TEMPLATE_ID_UUID = "شناسه قالب باید یک UUID معتبر باشد",
|
||||||
|
TEMPLATE_ACCESS_DENIED = "شما دسترسی به این قالب را ندارید",
|
||||||
|
TEMPLATE_BUSINESS_NOT_FOUND = "کسب و کار مالک قالب یافت نشد",
|
||||||
|
SEARCH_STRING = "نام برای جستجو باید یک رشته باشد",
|
||||||
|
TEMPLATE_SELECTED_SUCCESSFULLY = "وضعیت قالب با موفقیت تغییر کرد",
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { BusinessStaff } from "./business-staff.entity";
|
|||||||
import { BaseEntity } from "../../../common/entities/base.entity";
|
import { BaseEntity } from "../../../common/entities/base.entity";
|
||||||
import { Domain } from "../../domains/entities/domain.entity";
|
import { Domain } from "../../domains/entities/domain.entity";
|
||||||
import { EmailSignature } from "../../signatures/entities/email-signature.entity";
|
import { EmailSignature } from "../../signatures/entities/email-signature.entity";
|
||||||
|
import { Template } from "../../templates/entities/template.entity";
|
||||||
import { BusinessRepository } from "../repositories/business.repository";
|
import { BusinessRepository } from "../repositories/business.repository";
|
||||||
|
|
||||||
@Entity({ repository: () => BusinessRepository })
|
@Entity({ repository: () => BusinessRepository })
|
||||||
@@ -47,5 +48,8 @@ export class Business extends BaseEntity {
|
|||||||
@OneToMany(() => BusinessStaff, (businessStaff) => businessStaff.business)
|
@OneToMany(() => BusinessStaff, (businessStaff) => businessStaff.business)
|
||||||
businessStaffs = new Collection<BusinessStaff>(this);
|
businessStaffs = new Collection<BusinessStaff>(this);
|
||||||
|
|
||||||
|
@OneToMany(() => Template, (template) => template.business)
|
||||||
|
templates = new Collection<Template>(this);
|
||||||
|
|
||||||
[EntityRepositoryType]?: BusinessRepository;
|
[EntityRepositoryType]?: BusinessRepository;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { MikroOrmModule } from "@mikro-orm/nestjs";
|
||||||
import { Module } from "@nestjs/common";
|
import { Module } from "@nestjs/common";
|
||||||
import { JwtModule } from "@nestjs/jwt";
|
import { JwtModule } from "@nestjs/jwt";
|
||||||
|
|
||||||
@@ -9,12 +10,15 @@ 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 { User } from "../users/entities/user.entity";
|
||||||
import { UsersModule } from "../users/users.module";
|
import { UsersModule } from "../users/users.module";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [MailServerModule, UsersModule, JwtModule.registerAsync(jwtConfig())],
|
imports: [MailServerModule, UsersModule, TemplatesModule, JwtModule.registerAsync(jwtConfig()), MikroOrmModule.forFeature([User])],
|
||||||
controllers: [EmailController],
|
controllers: [EmailController],
|
||||||
providers: [EmailService, MailboxResolverService, EmailGateway, EmailNotificationService, WebSocketAuthService],
|
providers: [EmailService, MailboxResolverService, EmailGateway, EmailNotificationService, WebSocketAuthService, TemplateProcessorService],
|
||||||
exports: [EmailService, MailboxResolverService, EmailGateway, EmailNotificationService, WebSocketAuthService],
|
exports: [EmailService, MailboxResolverService, EmailGateway, EmailNotificationService, WebSocketAuthService, TemplateProcessorService],
|
||||||
})
|
})
|
||||||
export class EmailModule {}
|
export class EmailModule {}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { MailboxResolverService } from "./mailbox-resolver.service";
|
|||||||
import { EmailMessage } from "../../../common/enums/message.enum";
|
import { EmailMessage } from "../../../common/enums/message.enum";
|
||||||
import { MailServerService } from "../../mail-server/services/mail-server.service";
|
import { MailServerService } from "../../mail-server/services/mail-server.service";
|
||||||
import { MailboxEnum } from "../../mailbox/enums/mailbox.enum";
|
import { MailboxEnum } from "../../mailbox/enums/mailbox.enum";
|
||||||
|
import { TemplateProcessorService } from "../../templates/services/template-processor.service";
|
||||||
|
import { UserRepository } from "../../users/repositories/user.repository";
|
||||||
import { BulkActionDto, BulkActionType } from "../DTO/bulk-actions.dto";
|
import { BulkActionDto, BulkActionType } from "../DTO/bulk-actions.dto";
|
||||||
import { MessageListQueryDto, SearchMessagesQueryDto } from "../DTO/email-query.dto";
|
import { MessageListQueryDto, SearchMessagesQueryDto } from "../DTO/email-query.dto";
|
||||||
import { SendEmailDto, UpdateDraftDto } from "../DTO/send-email.dto";
|
import { SendEmailDto, UpdateDraftDto } from "../DTO/send-email.dto";
|
||||||
@@ -19,16 +21,39 @@ export class EmailService {
|
|||||||
private readonly mailServerService: MailServerService,
|
private readonly mailServerService: MailServerService,
|
||||||
private readonly mailboxResolverService: MailboxResolverService,
|
private readonly mailboxResolverService: MailboxResolverService,
|
||||||
private readonly emailGateway: EmailGateway,
|
private readonly emailGateway: EmailGateway,
|
||||||
// private readonly userService: UsersService,
|
private readonly templateProcessorService: TemplateProcessorService,
|
||||||
|
private readonly userRepository: UserRepository,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
//########################################################
|
//########################################################
|
||||||
async sendEmail(userEmailId: string, sendEmailDto: SendEmailDto) {
|
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(", ")}`);
|
this.logger.log(`Sending email from user ${userEmailId} to ${sendEmailDto.to.map((t) => t.address).join(", ")}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Get user's business information
|
||||||
|
const user = await this.userRepository.findOne({ wildduckUserId: userEmailId, deletedAt: null }, { populate: ["business"] });
|
||||||
|
|
||||||
|
if (user && user.business) {
|
||||||
|
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, {
|
||||||
|
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.hasTemplate) {
|
||||||
|
this.logger.log(`Email content processed through template for business: ${user.business.id}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.logger.log(`No business template found for user ${userEmailId}, sending original content`);
|
||||||
|
}
|
||||||
|
|
||||||
const response = await firstValueFrom(this.mailServerService.submission.submitMessage(userEmailId, sendEmailDto));
|
const response = await firstValueFrom(this.mailServerService.submission.submitMessage(userEmailId, sendEmailDto));
|
||||||
|
|
||||||
this.logger.log(`Email sent successfully: ${response.id} (Queue: ${response.queueId})`);
|
this.logger.log(`Email sent successfully: ${response.id} (Queue: ${response.queueId})`);
|
||||||
@@ -381,6 +406,24 @@ export class EmailService {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Process draft content through business template if available
|
||||||
|
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, {
|
||||||
|
text: updateDraftDto.text,
|
||||||
|
html: updateDraftDto.html,
|
||||||
|
subject: updateDraftDto.subject,
|
||||||
|
});
|
||||||
|
|
||||||
|
draftData.html = processedTemplate.html;
|
||||||
|
draftData.text = processedTemplate.text;
|
||||||
|
|
||||||
|
if (processedTemplate.hasTemplate) {
|
||||||
|
this.logger.log(`Draft content processed through template for business: ${user.business.id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const response = await firstValueFrom(this.mailServerService.submission.submitMessage(userEmailId, draftData));
|
const response = await firstValueFrom(this.mailServerService.submission.submitMessage(userEmailId, draftData));
|
||||||
|
|
||||||
this.logger.log(`Draft updated successfully: ${response.id}`);
|
this.logger.log(`Draft updated successfully: ${response.id}`);
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export class QuotaSyncService {
|
|||||||
/**
|
/**
|
||||||
* Sync quota usage every 2 hours
|
* Sync quota usage every 2 hours
|
||||||
*/
|
*/
|
||||||
@Cron(CronExpression.EVERY_10_SECONDS)
|
@Cron(CronExpression.EVERY_2_HOURS)
|
||||||
async syncAllUsersQuota(): Promise<void> {
|
async syncAllUsersQuota(): Promise<void> {
|
||||||
const em = this.em.fork();
|
const em = this.em.fork();
|
||||||
this.logger.log("Starting quota synchronization for all users");
|
this.logger.log("Starting quota synchronization for all users");
|
||||||
@@ -66,7 +66,7 @@ export class QuotaSyncService {
|
|||||||
/**
|
/**
|
||||||
* Sync business quota usage every 4 hours
|
* Sync business quota usage every 4 hours
|
||||||
*/
|
*/
|
||||||
@Cron(CronExpression.EVERY_10_SECONDS)
|
@Cron(CronExpression.EVERY_4_HOURS)
|
||||||
async syncAllBusinessQuotas(): Promise<void> {
|
async syncAllBusinessQuotas(): Promise<void> {
|
||||||
const em = this.em.fork();
|
const em = this.em.fork();
|
||||||
this.logger.log("Starting business quota synchronization");
|
this.logger.log("Starting business quota synchronization");
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
||||||
|
import { IsNotEmpty, IsOptional, IsString, MaxLength } from "class-validator";
|
||||||
|
|
||||||
|
import { TemplateMessage } from "../../../common/enums/message.enum";
|
||||||
|
// import { PersonalityDataType } from "../interfaces/structure.interface";
|
||||||
|
|
||||||
|
export class CreateTemplateDto {
|
||||||
|
@IsNotEmpty({ message: TemplateMessage.TEMPLATE_NAME_REQUIRED })
|
||||||
|
@IsString({ message: TemplateMessage.TEMPLATE_NAME_STRING })
|
||||||
|
@MaxLength(255, { message: TemplateMessage.TEMPLATE_NAME_MAX_LENGTH })
|
||||||
|
@ApiProperty({ description: "Template name", example: "Corporate Email Template", maxLength: 255 })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@IsNotEmpty({ message: TemplateMessage.TEMPLATE_STRUCTURE_REQUIRED })
|
||||||
|
// @ValidateNested({ message: TemplateMessage.TEMPLATE_STRUCTURE_VALID })
|
||||||
|
// @Type(() => Object)
|
||||||
|
@ApiProperty({
|
||||||
|
description: "Template structure containing header, content, and footer sections",
|
||||||
|
example: {
|
||||||
|
header: {
|
||||||
|
columnsCount: 1,
|
||||||
|
items: [],
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
columnsCount: 2,
|
||||||
|
items: [],
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
columnsCount: 1,
|
||||||
|
items: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
structure: Record<string, unknown>;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString({ message: TemplateMessage.TEMPLATE_RAW_HTML_STRING })
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: "Raw HTML content of the template (optional)",
|
||||||
|
example: "<html><body><h1>Template Content</h1></body></html>",
|
||||||
|
})
|
||||||
|
rawHtml?: string;
|
||||||
|
|
||||||
|
// @IsNotEmpty({ message: "شناسه کسب و کار الزامی است" })
|
||||||
|
// @IsUUID("7", { message: "شناسه کسب و کار باید یک UUID معتبر باشد" })
|
||||||
|
// @ApiProperty({
|
||||||
|
// description: "Business ID that owns this template",
|
||||||
|
// example: "01234567-89ab-cdef-0123-456789abcdef",
|
||||||
|
// })
|
||||||
|
// businessId: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export { CreateTemplateDto } from "./create-template.dto";
|
||||||
|
export { UpdateTemplateDto } from "./update-template.dto";
|
||||||
|
export { TemplateQueryDto } from "./template-query.dto";
|
||||||
|
export { TemplateParamDto } from "./template-param.dto";
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
|
import { IsNotEmpty, IsUUID } from "class-validator";
|
||||||
|
|
||||||
|
import { TemplateMessage } from "../../../common/enums/message.enum";
|
||||||
|
|
||||||
|
export class TemplateParamDto {
|
||||||
|
@IsNotEmpty({ message: TemplateMessage.TEMPLATE_ID_REQUIRED })
|
||||||
|
@IsUUID("7", { message: TemplateMessage.TEMPLATE_ID_UUID })
|
||||||
|
@ApiProperty({
|
||||||
|
description: "Template ID",
|
||||||
|
example: "01234567-89ab-cdef-0123-456789abcdef",
|
||||||
|
})
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { ApiPropertyOptional } from "@nestjs/swagger";
|
||||||
|
import { IsOptional, IsString, IsUUID } from "class-validator";
|
||||||
|
|
||||||
|
import { PaginationDto } from "../../../common/DTO/pagination.dto";
|
||||||
|
import { TemplateMessage } from "../../../common/enums/message.enum";
|
||||||
|
|
||||||
|
export class TemplateQueryDto extends PaginationDto {
|
||||||
|
@IsOptional()
|
||||||
|
@IsString({ message: TemplateMessage.SEARCH_STRING })
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: "Search by template name",
|
||||||
|
example: "corporate",
|
||||||
|
})
|
||||||
|
search?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsUUID("7", { message: TemplateMessage.TEMPLATE_BUSINESS_ID_UUID })
|
||||||
|
@ApiPropertyOptional({ description: "Filter by business ID", example: "01234567-89ab-cdef-0123-456789abcdef" })
|
||||||
|
businessId?: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { ApiPropertyOptional } from "@nestjs/swagger";
|
||||||
|
import { Type } from "class-transformer";
|
||||||
|
import { IsOptional, IsString, MaxLength, ValidateNested } from "class-validator";
|
||||||
|
|
||||||
|
import { TemplateMessage } from "../../../common/enums/message.enum";
|
||||||
|
import { PersonalityDataType } from "../interfaces/structure.interface";
|
||||||
|
|
||||||
|
export class UpdateTemplateDto {
|
||||||
|
@IsOptional()
|
||||||
|
@IsString({ message: TemplateMessage.TEMPLATE_NAME_STRING })
|
||||||
|
@MaxLength(255, { message: TemplateMessage.TEMPLATE_NAME_MAX_LENGTH })
|
||||||
|
@ApiPropertyOptional({ description: "Template name", example: "Updated Corporate Email Template", maxLength: 255 })
|
||||||
|
name?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@ValidateNested({ message: TemplateMessage.TEMPLATE_STRUCTURE_VALID })
|
||||||
|
@Type(() => Object)
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: "Template structure containing header, content, and footer sections",
|
||||||
|
example: {
|
||||||
|
header: {
|
||||||
|
columnsCount: 1,
|
||||||
|
items: [],
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
columnsCount: 2,
|
||||||
|
items: [],
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
columnsCount: 1,
|
||||||
|
items: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
structure?: PersonalityDataType;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString({ message: TemplateMessage.TEMPLATE_RAW_HTML_STRING })
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: "Raw HTML content of the template",
|
||||||
|
example: "<html><body><h1>Updated Template Content</h1></body></html>",
|
||||||
|
})
|
||||||
|
rawHtml?: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { Collection, Entity, EntityRepositoryType, ManyToOne, OneToMany, Opt, Property } from "@mikro-orm/core";
|
||||||
|
import { v7 as uuidv7 } from "uuid";
|
||||||
|
|
||||||
|
import { BaseEntity } from "../../../common/entities/base.entity";
|
||||||
|
import { Business } from "../../businesses/entities/business.entity";
|
||||||
|
import { User } from "../../users/entities/user.entity";
|
||||||
|
// import { PersonalityDataType } from "../interfaces/structure.interface";
|
||||||
|
import { TemplateRepository } from "../repositories/template.repository";
|
||||||
|
|
||||||
|
@Entity({ repository: () => TemplateRepository })
|
||||||
|
export class Template extends BaseEntity {
|
||||||
|
@Property({ type: "varchar", length: 255, nullable: false })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@Property({ type: "jsonb" })
|
||||||
|
structure: unknown;
|
||||||
|
|
||||||
|
@Property({ type: "text", nullable: true })
|
||||||
|
rawHtml?: string;
|
||||||
|
|
||||||
|
@Property({ type: "uuid", onCreate: () => uuidv7() })
|
||||||
|
currentVersion: string;
|
||||||
|
|
||||||
|
@Property({ type: "boolean", default: false })
|
||||||
|
selected: boolean & Opt;
|
||||||
|
|
||||||
|
//--------------------------------------------------
|
||||||
|
|
||||||
|
@OneToMany(() => User, (user) => user.template)
|
||||||
|
users = new Collection<User>(this);
|
||||||
|
|
||||||
|
//--------------------------------------------------
|
||||||
|
|
||||||
|
@ManyToOne(() => Business, { nullable: false, deleteRule: "cascade" })
|
||||||
|
business!: Business;
|
||||||
|
|
||||||
|
[EntityRepositoryType]?: TemplateRepository;
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
export enum HorizontalAlignment {
|
||||||
|
LEFT = "left",
|
||||||
|
CENTER = "center",
|
||||||
|
RIGHT = "right",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum VerticalAlignment {
|
||||||
|
TOP = "top",
|
||||||
|
MIDDLE = "middle",
|
||||||
|
BOTTOM = "bottom",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ButtonSize {
|
||||||
|
SMALL = "small",
|
||||||
|
MEDIUM = "medium",
|
||||||
|
LARGE = "large",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ImageSize {
|
||||||
|
COVER = "cover",
|
||||||
|
CONTAIN = "contain",
|
||||||
|
AUTO = "auto",
|
||||||
|
REPEAT = "repeat",
|
||||||
|
REPEAT_X = "repeat-x",
|
||||||
|
REPEAT_Y = "repeat-y",
|
||||||
|
NO_REPEAT = "no-repeat",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum BackgroundSize {
|
||||||
|
COVER = "cover",
|
||||||
|
CONTAIN = "contain",
|
||||||
|
ROUND = "round",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum SectionName {
|
||||||
|
HEADER = "header",
|
||||||
|
CONTENT = "content",
|
||||||
|
FOOTER = "footer",
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PersonalityDataType = {
|
||||||
|
header: SectionType;
|
||||||
|
content: SectionType;
|
||||||
|
footer: SectionType;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SectionType = {
|
||||||
|
columnsCount: number;
|
||||||
|
items: SectionItemType[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SectionItemType = {
|
||||||
|
id: string;
|
||||||
|
backgroundColor: string;
|
||||||
|
backgroundImage?: string;
|
||||||
|
texts: TextType[];
|
||||||
|
buttons: ButtonType[];
|
||||||
|
images: ImageType[];
|
||||||
|
alignment?: HorizontalAlignment;
|
||||||
|
verticalAlignment?: VerticalAlignment;
|
||||||
|
style?: {
|
||||||
|
borderRadius?: number;
|
||||||
|
padding?: string;
|
||||||
|
fontFamily?: string;
|
||||||
|
backgroundSize?: string;
|
||||||
|
backgroundRepeat?: string;
|
||||||
|
backgroundPosition?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TextType = {
|
||||||
|
id: string;
|
||||||
|
text: string;
|
||||||
|
fontSize?: number;
|
||||||
|
fontFamily?: string;
|
||||||
|
color?: string;
|
||||||
|
alignment?: HorizontalAlignment;
|
||||||
|
verticalAlignment?: VerticalAlignment;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ButtonType = {
|
||||||
|
id: string;
|
||||||
|
text: string;
|
||||||
|
link: string;
|
||||||
|
size: ButtonSize;
|
||||||
|
isBorder: boolean;
|
||||||
|
borderSize: number;
|
||||||
|
textColor: string;
|
||||||
|
backgroundColor: string;
|
||||||
|
borderColor: string;
|
||||||
|
alignment?: HorizontalAlignment;
|
||||||
|
verticalAlignment?: VerticalAlignment;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ImageType = {
|
||||||
|
id: string;
|
||||||
|
src: string;
|
||||||
|
alt?: string;
|
||||||
|
size: ImageSize;
|
||||||
|
width?: string;
|
||||||
|
height?: string;
|
||||||
|
alignment?: HorizontalAlignment;
|
||||||
|
verticalAlignment?: VerticalAlignment;
|
||||||
|
};
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { EntityRepository, FilterQuery } from "@mikro-orm/postgresql";
|
||||||
|
|
||||||
|
import { PaginationUtils } from "../../utils/services/pagination.utils";
|
||||||
|
import { TemplateQueryDto } from "../DTO/template-query.dto";
|
||||||
|
import { Template } from "../entities/template.entity";
|
||||||
|
|
||||||
|
export class TemplateRepository extends EntityRepository<Template> {
|
||||||
|
async findAllTemplates(queryDto: TemplateQueryDto, businessId: string) {
|
||||||
|
const { limit, skip } = PaginationUtils(queryDto);
|
||||||
|
|
||||||
|
const query: FilterQuery<Template> = {
|
||||||
|
deletedAt: null,
|
||||||
|
business: {
|
||||||
|
id: businessId,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (queryDto.search) {
|
||||||
|
query.name = { $ilike: `%${queryDto.search}%` };
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.findAndCount(query, { limit, offset: skip, populate: ["business"], exclude: ["rawHtml", "structure"] });
|
||||||
|
}
|
||||||
|
|
||||||
|
//**************************************************** */
|
||||||
|
async findTemplateById(id: string, businessId: string) {
|
||||||
|
const template = await this.findOne({ id, business: { id: businessId }, deletedAt: null }, { populate: ["business"] });
|
||||||
|
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
//**************************************************** */
|
||||||
|
}
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TemplateProcessorService {
|
||||||
|
private readonly logger = new Logger(TemplateProcessorService.name);
|
||||||
|
|
||||||
|
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<ProcessedTemplateResult> {
|
||||||
|
try {
|
||||||
|
this.logger.log(`Processing email with template for business: ${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`);
|
||||||
|
return {
|
||||||
|
html: emailContent.html || this.convertTextToHtml(emailContent.text || ""),
|
||||||
|
text: emailContent.text || this.stripHtmlTags(emailContent.html || ""),
|
||||||
|
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);
|
||||||
|
|
||||||
|
return {
|
||||||
|
html: processedContent.html,
|
||||||
|
text: processedContent.text,
|
||||||
|
hasTemplate: true,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`Failed to process email with template for business ${businessId}:`, error);
|
||||||
|
|
||||||
|
// Fallback to original content if template processing fails
|
||||||
|
return {
|
||||||
|
html: emailContent.html || this.convertTextToHtml(emailContent.text || ""),
|
||||||
|
text: emailContent.text || this.stripHtmlTags(emailContent.html || ""),
|
||||||
|
hasTemplate: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject email content into template structure
|
||||||
|
*/
|
||||||
|
private async injectContentIntoTemplate(
|
||||||
|
// 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);
|
||||||
|
return {
|
||||||
|
html,
|
||||||
|
text: this.stripHtmlTags(html),
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error("Failed to inject content into template:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
const contentToInject = emailContent.html || this.convertTextToHtml(emailContent.text || "");
|
||||||
|
|
||||||
|
// Common placeholder patterns
|
||||||
|
const placeholders = [/\{\{content\}\}/gi, /\{\{message\}\}/gi, /\{\{body\}\}/gi, /\{content\}/gi, /\{message\}/gi, /\{body\}/gi];
|
||||||
|
|
||||||
|
placeholders.forEach((placeholder) => {
|
||||||
|
processedHtml = processedHtml.replace(placeholder, contentToInject);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Replace subject placeholders
|
||||||
|
if (emailContent.subject) {
|
||||||
|
const subjectPlaceholders = [/\{\{subject\}\}/gi, /\{\{title\}\}/gi, /\{subject\}/gi, /\{title\}/gi];
|
||||||
|
|
||||||
|
subjectPlaceholders.forEach((placeholder) => {
|
||||||
|
processedHtml = processedHtml.replace(placeholder, emailContent.subject!);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return html
|
||||||
|
.replace(/<[^>]*>/g, "")
|
||||||
|
.replace(/ /g, " ")
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
import { EntityManager } from "@mikro-orm/postgresql";
|
||||||
|
import { BadRequestException, Injectable } from "@nestjs/common";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import { v7 as uuidv7 } from "uuid";
|
||||||
|
|
||||||
|
import { TemplateMessage } from "../../../common/enums/message.enum";
|
||||||
|
import { BusinessRepository } from "../../businesses/repositories/business.repository";
|
||||||
|
import { CreateTemplateDto } from "../DTO/create-template.dto";
|
||||||
|
import { TemplateQueryDto } from "../DTO/template-query.dto";
|
||||||
|
import { UpdateTemplateDto } from "../DTO/update-template.dto";
|
||||||
|
import { TemplateRepository } from "../repositories/template.repository";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TemplatesService {
|
||||||
|
constructor(
|
||||||
|
private readonly templateRepository: TemplateRepository,
|
||||||
|
private readonly businessRepository: BusinessRepository,
|
||||||
|
private readonly em: EntityManager,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async createTemplate(createTemplateDto: CreateTemplateDto, businessId: string) {
|
||||||
|
const business = await this.businessRepository.findOne({
|
||||||
|
id: businessId,
|
||||||
|
deletedAt: null,
|
||||||
|
});
|
||||||
|
if (!business) throw new BadRequestException(TemplateMessage.TEMPLATE_BUSINESS_NOT_FOUND);
|
||||||
|
|
||||||
|
const template = this.templateRepository.create({
|
||||||
|
name: createTemplateDto.name,
|
||||||
|
structure: createTemplateDto.structure,
|
||||||
|
rawHtml: createTemplateDto.rawHtml,
|
||||||
|
currentVersion: uuidv7(),
|
||||||
|
business,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.em.persistAndFlush(template);
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: TemplateMessage.TEMPLATE_CREATED_SUCCESSFULLY,
|
||||||
|
template,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//**************************************************** */
|
||||||
|
async findAllTemplates(queryDto: TemplateQueryDto, businessId: string) {
|
||||||
|
const [templates, count] = await this.templateRepository.findAllTemplates(queryDto, businessId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
templates,
|
||||||
|
count,
|
||||||
|
pagination: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//**************************************************** */
|
||||||
|
async findTemplateById(id: string, businessId: string) {
|
||||||
|
const template = await this.templateRepository.findTemplateById(id, businessId);
|
||||||
|
|
||||||
|
if (!template) throw new BadRequestException(TemplateMessage.TEMPLATE_NOT_FOUND);
|
||||||
|
|
||||||
|
return {
|
||||||
|
template,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//**************************************************** */
|
||||||
|
async updateTemplate(id: string, updateTemplateDto: UpdateTemplateDto, businessId: string) {
|
||||||
|
const template = await this.templateRepository.findTemplateById(id, businessId);
|
||||||
|
|
||||||
|
if (!template) throw new BadRequestException(TemplateMessage.TEMPLATE_NOT_FOUND);
|
||||||
|
|
||||||
|
this.templateRepository.assign(template, updateTemplateDto);
|
||||||
|
|
||||||
|
if (updateTemplateDto.structure) {
|
||||||
|
template.currentVersion = uuidv7();
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.em.flush();
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: TemplateMessage.TEMPLATE_UPDATED_SUCCESSFULLY,
|
||||||
|
template,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//**************************************************** */
|
||||||
|
async deleteTemplate(id: string, businessId: string) {
|
||||||
|
const template = await this.templateRepository.findTemplateById(id, businessId);
|
||||||
|
|
||||||
|
if (!template) throw new BadRequestException(TemplateMessage.TEMPLATE_NOT_FOUND);
|
||||||
|
|
||||||
|
template.deletedAt = dayjs().toDate();
|
||||||
|
await this.em.flush();
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: TemplateMessage.TEMPLATE_DELETED_SUCCESSFULLY,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//**************************************************** */
|
||||||
|
async getTemplatesByBusinessId(businessId: string) {
|
||||||
|
return this.templateRepository.find(
|
||||||
|
{
|
||||||
|
business: { id: businessId },
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
populate: ["business"],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//**************************************************** */
|
||||||
|
async setSelectedTemplate(id: string, businessId: string) {
|
||||||
|
const template = await this.templateRepository.findTemplateById(id, businessId);
|
||||||
|
|
||||||
|
if (!template) throw new BadRequestException(TemplateMessage.TEMPLATE_NOT_FOUND);
|
||||||
|
|
||||||
|
// First, unselect all templates for this business
|
||||||
|
await this.templateRepository.nativeUpdate({ business: { id: businessId }, deletedAt: null }, { selected: false });
|
||||||
|
|
||||||
|
// Then select the specified template
|
||||||
|
template.selected = true;
|
||||||
|
await this.em.flush();
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: TemplateMessage.TEMPLATE_SELECTED_SUCCESSFULLY,
|
||||||
|
template,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { Body, Controller, Delete, Get, Param, Patch, Post, Query, UseInterceptors } from "@nestjs/common";
|
||||||
|
import { ApiHeader, ApiOperation, ApiResponse } from "@nestjs/swagger";
|
||||||
|
|
||||||
|
import { CreateTemplateDto } from "./DTO/create-template.dto";
|
||||||
|
import { TemplateParamDto } from "./DTO/template-param.dto";
|
||||||
|
import { TemplateQueryDto } from "./DTO/template-query.dto";
|
||||||
|
import { UpdateTemplateDto } from "./DTO/update-template.dto";
|
||||||
|
import { TemplatesService } from "./services/templates.service";
|
||||||
|
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
|
||||||
|
import { BusinessDec } from "../../common/decorators/business.decorator";
|
||||||
|
import { BusinessInterceptor } from "../../core/interceptors/business.interceptor";
|
||||||
|
|
||||||
|
@Controller("templates")
|
||||||
|
@ApiHeader({ name: "x-business-id", description: "Business ID" })
|
||||||
|
@UseInterceptors(BusinessInterceptor)
|
||||||
|
@AuthGuards()
|
||||||
|
export class TemplatesController {
|
||||||
|
constructor(private readonly templatesService: TemplatesService) {}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
@ApiOperation({ summary: "Create a new template" })
|
||||||
|
@ApiResponse({ status: 201, description: "Template created successfully" })
|
||||||
|
@ApiResponse({ status: 400, description: "Bad request - invalid data" })
|
||||||
|
@ApiResponse({ status: 404, description: "Business not found" })
|
||||||
|
createTemplate(@Body() createTemplateDto: CreateTemplateDto, @BusinessDec("id") businessId: string) {
|
||||||
|
return this.templatesService.createTemplate(createTemplateDto, businessId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@ApiOperation({ summary: "Get all templates for business" })
|
||||||
|
@ApiResponse({ status: 200, description: "Templates retrieved successfully" })
|
||||||
|
findAllTemplates(@Query() queryDto: TemplateQueryDto, @BusinessDec("id") businessId: string) {
|
||||||
|
return this.templatesService.findAllTemplates(queryDto, businessId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(":id")
|
||||||
|
@ApiOperation({ summary: "Get template by ID" })
|
||||||
|
@ApiResponse({ status: 200, description: "Template retrieved successfully" })
|
||||||
|
@ApiResponse({ status: 404, description: "Template not found" })
|
||||||
|
findTemplateById(@Param() params: TemplateParamDto, @BusinessDec("id") businessId: string) {
|
||||||
|
return this.templatesService.findTemplateById(params.id, businessId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch(":id")
|
||||||
|
@ApiOperation({ summary: "Update template by ID" })
|
||||||
|
@ApiResponse({ status: 200, description: "Template updated successfully" })
|
||||||
|
@ApiResponse({ status: 404, description: "Template not found" })
|
||||||
|
updateTemplate(@Param() params: TemplateParamDto, @Body() updateTemplateDto: UpdateTemplateDto, @BusinessDec("id") businessId: string) {
|
||||||
|
return this.templatesService.updateTemplate(params.id, updateTemplateDto, businessId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(":id")
|
||||||
|
@ApiOperation({ summary: "Delete template by ID" })
|
||||||
|
@ApiResponse({ status: 200, description: "Template deleted successfully" })
|
||||||
|
@ApiResponse({ status: 404, description: "Template not found" })
|
||||||
|
deleteTemplate(@Param() params: TemplateParamDto, @BusinessDec("id") businessId: string) {
|
||||||
|
return this.templatesService.deleteTemplate(params.id, businessId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post(":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" })
|
||||||
|
setSelectedTemplate(@Param() params: TemplateParamDto, @BusinessDec("id") businessId: string) {
|
||||||
|
return this.templatesService.setSelectedTemplate(params.id, businessId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { MikroOrmModule } from "@mikro-orm/nestjs";
|
||||||
|
import { Module } from "@nestjs/common";
|
||||||
|
|
||||||
|
import { Template } from "./entities/template.entity";
|
||||||
|
import { TemplateProcessorService } from "./services/template-processor.service";
|
||||||
|
import { TemplatesService } from "./services/templates.service";
|
||||||
|
import { TemplatesController } from "./templates.controller";
|
||||||
|
import { BusinessesModule } from "../businesses/businesses.module";
|
||||||
|
import { Business } from "../businesses/entities/business.entity";
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [MikroOrmModule.forFeature([Template, Business]), BusinessesModule],
|
||||||
|
providers: [TemplatesService, TemplateProcessorService],
|
||||||
|
controllers: [TemplatesController],
|
||||||
|
exports: [TemplatesService, TemplateProcessorService],
|
||||||
|
})
|
||||||
|
export class TemplatesModule {}
|
||||||
@@ -5,6 +5,7 @@ import { Role } from "./role.entity";
|
|||||||
import { BaseEntity } from "../../../common/entities/base.entity";
|
import { BaseEntity } from "../../../common/entities/base.entity";
|
||||||
import { Business } from "../../businesses/entities/business.entity";
|
import { Business } from "../../businesses/entities/business.entity";
|
||||||
import { Domain } from "../../domains/entities/domain.entity";
|
import { Domain } from "../../domains/entities/domain.entity";
|
||||||
|
import { Template } from "../../templates/entities/template.entity";
|
||||||
import { UserRepository } from "../repositories/user.repository";
|
import { UserRepository } from "../repositories/user.repository";
|
||||||
|
|
||||||
@Entity({ repository: () => UserRepository })
|
@Entity({ repository: () => UserRepository })
|
||||||
@@ -52,11 +53,14 @@ export class User extends BaseEntity {
|
|||||||
@ManyToOne(() => Role, { deleteRule: "restrict" })
|
@ManyToOne(() => Role, { deleteRule: "restrict" })
|
||||||
role!: Role;
|
role!: Role;
|
||||||
|
|
||||||
@ManyToOne(() => Business, { deleteRule: "restrict" })
|
@ManyToOne(() => Business, { deleteRule: "cascade" })
|
||||||
business!: Business;
|
business!: Business;
|
||||||
|
|
||||||
@ManyToOne(() => Domain, { deleteRule: "restrict" })
|
@ManyToOne(() => Domain, { deleteRule: "restrict" })
|
||||||
domain!: Domain;
|
domain!: Domain;
|
||||||
|
|
||||||
|
@ManyToOne(() => Template, { deleteRule: "restrict", nullable: true })
|
||||||
|
template?: Template;
|
||||||
|
|
||||||
[EntityRepositoryType]?: UserRepository;
|
[EntityRepositoryType]?: UserRepository;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user