@flusys/nestjs-email 1.1.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cjs/config/email-config.service.js +94 -0
- package/cjs/config/email.constants.js +40 -0
- package/cjs/config/index.js +19 -0
- package/cjs/controllers/email-config.controller.js +59 -0
- package/cjs/controllers/email-send.controller.js +142 -0
- package/cjs/controllers/email-template.controller.js +84 -0
- package/cjs/controllers/index.js +20 -0
- package/cjs/docs/email-swagger.config.js +176 -0
- package/cjs/docs/index.js +11 -0
- package/cjs/dtos/email-config.dto.js +238 -0
- package/cjs/dtos/email-send.dto.js +376 -0
- package/cjs/dtos/email-template.dto.js +283 -0
- package/cjs/dtos/index.js +20 -0
- package/cjs/entities/email-config-base.entity.js +111 -0
- package/cjs/entities/email-config-with-company.entity.js +63 -0
- package/cjs/entities/email-config.entity.js +25 -0
- package/cjs/entities/email-template-base.entity.js +134 -0
- package/cjs/entities/email-template-with-company.entity.js +63 -0
- package/cjs/entities/email-template.entity.js +25 -0
- package/cjs/entities/index.js +41 -0
- package/cjs/enums/email-provider-type.enum.js +18 -0
- package/cjs/enums/index.js +18 -0
- package/cjs/index.js +27 -0
- package/cjs/interfaces/email-config.interface.js +4 -0
- package/cjs/interfaces/email-module-options.interface.js +4 -0
- package/cjs/interfaces/email-provider.interface.js +6 -0
- package/cjs/interfaces/email-template.interface.js +4 -0
- package/cjs/interfaces/index.js +21 -0
- package/cjs/modules/email.module.js +177 -0
- package/cjs/modules/index.js +18 -0
- package/cjs/providers/email-factory.service.js +160 -0
- package/cjs/providers/email-provider.registry.js +51 -0
- package/cjs/providers/index.js +22 -0
- package/cjs/providers/mailgun-provider.js +125 -0
- package/cjs/providers/sendgrid-provider.js +156 -0
- package/cjs/providers/smtp-provider.js +185 -0
- package/cjs/services/email-datasource.provider.js +215 -0
- package/cjs/services/email-provider-config.service.js +180 -0
- package/cjs/services/email-send.service.js +228 -0
- package/cjs/services/email-template.service.js +186 -0
- package/cjs/services/index.js +21 -0
- package/config/email-config.service.d.ts +13 -0
- package/config/email.constants.d.ts +11 -0
- package/config/index.d.ts +2 -0
- package/controllers/email-config.controller.d.ts +17 -0
- package/controllers/email-send.controller.d.ts +19 -0
- package/controllers/email-template.controller.d.ts +25 -0
- package/controllers/index.d.ts +3 -0
- package/docs/email-swagger.config.d.ts +3 -0
- package/docs/index.d.ts +1 -0
- package/dtos/email-config.dto.d.ts +33 -0
- package/dtos/email-send.dto.d.ts +45 -0
- package/dtos/email-template.dto.d.ts +42 -0
- package/dtos/index.d.ts +3 -0
- package/entities/email-config-base.entity.d.ts +11 -0
- package/entities/email-config-with-company.entity.d.ts +4 -0
- package/entities/email-config.entity.d.ts +3 -0
- package/entities/email-template-base.entity.d.ts +14 -0
- package/entities/email-template-with-company.entity.d.ts +4 -0
- package/entities/email-template.entity.d.ts +3 -0
- package/entities/index.d.ts +7 -0
- package/enums/email-provider-type.enum.d.ts +5 -0
- package/enums/index.d.ts +1 -0
- package/fesm/config/email-config.service.js +84 -0
- package/fesm/config/email.constants.js +13 -0
- package/fesm/config/index.js +2 -0
- package/fesm/controllers/email-config.controller.js +49 -0
- package/fesm/controllers/email-send.controller.js +132 -0
- package/fesm/controllers/email-template.controller.js +74 -0
- package/fesm/controllers/index.js +3 -0
- package/fesm/docs/email-swagger.config.js +172 -0
- package/fesm/docs/index.js +1 -0
- package/fesm/dtos/email-config.dto.js +223 -0
- package/fesm/dtos/email-send.dto.js +360 -0
- package/fesm/dtos/email-template.dto.js +268 -0
- package/fesm/dtos/index.js +3 -0
- package/fesm/entities/email-config-base.entity.js +101 -0
- package/fesm/entities/email-config-with-company.entity.js +53 -0
- package/fesm/entities/email-config.entity.js +15 -0
- package/fesm/entities/email-template-base.entity.js +124 -0
- package/fesm/entities/email-template-with-company.entity.js +53 -0
- package/fesm/entities/email-template.entity.js +15 -0
- package/fesm/entities/index.js +20 -0
- package/fesm/enums/email-provider-type.enum.js +8 -0
- package/fesm/enums/index.js +1 -0
- package/fesm/index.js +10 -0
- package/fesm/interfaces/email-config.interface.js +3 -0
- package/fesm/interfaces/email-module-options.interface.js +3 -0
- package/fesm/interfaces/email-provider.interface.js +5 -0
- package/fesm/interfaces/email-template.interface.js +3 -0
- package/fesm/interfaces/index.js +4 -0
- package/fesm/modules/email.module.js +167 -0
- package/fesm/modules/index.js +1 -0
- package/fesm/providers/email-factory.service.js +109 -0
- package/fesm/providers/email-provider.registry.js +44 -0
- package/fesm/providers/index.js +5 -0
- package/fesm/providers/mailgun-provider.js +119 -0
- package/fesm/providers/sendgrid-provider.js +150 -0
- package/fesm/providers/smtp-provider.js +137 -0
- package/fesm/services/email-datasource.provider.js +164 -0
- package/fesm/services/email-provider-config.service.js +170 -0
- package/fesm/services/email-send.service.js +218 -0
- package/fesm/services/email-template.service.js +176 -0
- package/fesm/services/index.js +4 -0
- package/index.d.ts +9 -0
- package/interfaces/email-config.interface.d.ts +28 -0
- package/interfaces/email-module-options.interface.d.ts +26 -0
- package/interfaces/email-provider.interface.d.ts +34 -0
- package/interfaces/email-template.interface.d.ts +64 -0
- package/interfaces/index.d.ts +4 -0
- package/modules/email.module.d.ts +9 -0
- package/modules/index.d.ts +1 -0
- package/package.json +105 -0
- package/providers/email-factory.service.d.ts +14 -0
- package/providers/email-provider.registry.d.ts +10 -0
- package/providers/index.d.ts +5 -0
- package/providers/mailgun-provider.d.ts +11 -0
- package/providers/sendgrid-provider.d.ts +11 -0
- package/providers/smtp-provider.d.ts +11 -0
- package/services/email-datasource.provider.d.ts +25 -0
- package/services/email-provider-config.service.d.ts +32 -0
- package/services/email-send.service.d.ts +20 -0
- package/services/email-template.service.d.ts +31 -0
- package/services/index.d.ts +4 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "EmailProviderConfigService", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return EmailProviderConfigService;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const _classes = require("@flusys/nestjs-shared/classes");
|
|
12
|
+
const _modules = require("@flusys/nestjs-shared/modules");
|
|
13
|
+
const _common = require("@nestjs/common");
|
|
14
|
+
const _config = require("../config");
|
|
15
|
+
const _entities = require("../entities");
|
|
16
|
+
const _emaildatasourceprovider = require("./email-datasource.provider");
|
|
17
|
+
function _define_property(obj, key, value) {
|
|
18
|
+
if (key in obj) {
|
|
19
|
+
Object.defineProperty(obj, key, {
|
|
20
|
+
value: value,
|
|
21
|
+
enumerable: true,
|
|
22
|
+
configurable: true,
|
|
23
|
+
writable: true
|
|
24
|
+
});
|
|
25
|
+
} else {
|
|
26
|
+
obj[key] = value;
|
|
27
|
+
}
|
|
28
|
+
return obj;
|
|
29
|
+
}
|
|
30
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
31
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
32
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
33
|
+
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
34
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
35
|
+
}
|
|
36
|
+
function _ts_metadata(k, v) {
|
|
37
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
38
|
+
}
|
|
39
|
+
function _ts_param(paramIndex, decorator) {
|
|
40
|
+
return function(target, key) {
|
|
41
|
+
decorator(target, key, paramIndex);
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
let EmailProviderConfigService = class EmailProviderConfigService extends _classes.RequestScopedApiService {
|
|
45
|
+
/**
|
|
46
|
+
* Resolve entity class for this service
|
|
47
|
+
*/ resolveEntity() {
|
|
48
|
+
const enableCompanyFeature = this.emailConfig.isCompanyFeatureEnabled();
|
|
49
|
+
return enableCompanyFeature ? _entities.EmailConfigWithCompany : _entities.EmailConfig;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Get DataSource provider for this service
|
|
53
|
+
*/ getDataSourceProvider() {
|
|
54
|
+
return this.dataSourceProvider;
|
|
55
|
+
}
|
|
56
|
+
async convertSingleDtoToEntity(dto, user) {
|
|
57
|
+
let emailConfigEntity = {
|
|
58
|
+
...dto
|
|
59
|
+
};
|
|
60
|
+
// Set company fields if company feature is enabled
|
|
61
|
+
if (this.emailConfig.isCompanyFeatureEnabled()) {
|
|
62
|
+
emailConfigEntity.companyId = user?.companyId ?? null;
|
|
63
|
+
}
|
|
64
|
+
return emailConfigEntity;
|
|
65
|
+
}
|
|
66
|
+
async getSelectQuery(query, _user, select) {
|
|
67
|
+
if (!select || !select.length) {
|
|
68
|
+
select = [
|
|
69
|
+
'id',
|
|
70
|
+
'name',
|
|
71
|
+
'provider',
|
|
72
|
+
'config',
|
|
73
|
+
'fromEmail',
|
|
74
|
+
'fromName',
|
|
75
|
+
'isActive',
|
|
76
|
+
'isDefault',
|
|
77
|
+
'createdAt',
|
|
78
|
+
'updatedAt'
|
|
79
|
+
];
|
|
80
|
+
if (this.emailConfig.isCompanyFeatureEnabled()) {
|
|
81
|
+
select.push('companyId');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const selectFields = select.map((field)=>`${this.entityName}.${field}`);
|
|
85
|
+
query.select(selectFields);
|
|
86
|
+
return {
|
|
87
|
+
query,
|
|
88
|
+
isRaw: false
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Override: Extra query manipulation - Auto-filter by user's company
|
|
93
|
+
*/ async getExtraManipulateQuery(query, filterDto, user) {
|
|
94
|
+
const result = await super.getExtraManipulateQuery(query, filterDto, user);
|
|
95
|
+
const enableCompanyFeature = this.emailConfig.isCompanyFeatureEnabled();
|
|
96
|
+
if (enableCompanyFeature && user?.companyId) {
|
|
97
|
+
query.andWhere('emailConfig.companyId = :companyId', {
|
|
98
|
+
companyId: user.companyId
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
query.orderBy(`${this.entityName}.createdAt`, 'DESC');
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Find config by ID directly (bypasses company filtering)
|
|
106
|
+
*/ async findByIdDirect(id) {
|
|
107
|
+
await this.ensureRepositoryInitialized();
|
|
108
|
+
return await this.repository.findOne({
|
|
109
|
+
where: {
|
|
110
|
+
id
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Get default email configuration (scoped to user's company if enabled)
|
|
116
|
+
* Priority: isDefault=true > any active config (oldest first)
|
|
117
|
+
*/ async getDefaultConfig(user) {
|
|
118
|
+
await this.ensureRepositoryInitialized();
|
|
119
|
+
const baseWhere = {
|
|
120
|
+
isActive: true
|
|
121
|
+
};
|
|
122
|
+
if (this.emailConfig.isCompanyFeatureEnabled() && user?.companyId) {
|
|
123
|
+
baseWhere.companyId = user.companyId;
|
|
124
|
+
}
|
|
125
|
+
// First try to find config marked as default
|
|
126
|
+
const defaultConfig = await this.repository.findOne({
|
|
127
|
+
where: {
|
|
128
|
+
...baseWhere,
|
|
129
|
+
isDefault: true
|
|
130
|
+
},
|
|
131
|
+
order: {
|
|
132
|
+
createdAt: 'ASC'
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
if (defaultConfig) {
|
|
136
|
+
return defaultConfig;
|
|
137
|
+
}
|
|
138
|
+
// Fall back to any available active config (oldest first for consistency)
|
|
139
|
+
return await this.repository.findOne({
|
|
140
|
+
where: baseWhere,
|
|
141
|
+
order: {
|
|
142
|
+
createdAt: 'ASC'
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Get config by provider type
|
|
148
|
+
*/ async getConfigByProvider(provider, user) {
|
|
149
|
+
await this.ensureRepositoryInitialized();
|
|
150
|
+
const where = {
|
|
151
|
+
provider,
|
|
152
|
+
isActive: true
|
|
153
|
+
};
|
|
154
|
+
if (this.emailConfig.isCompanyFeatureEnabled() && user?.companyId) {
|
|
155
|
+
where.companyId = user.companyId;
|
|
156
|
+
}
|
|
157
|
+
return await this.repository.find({
|
|
158
|
+
where
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
constructor(cacheManager, utilsService, emailConfig, dataSourceProvider){
|
|
162
|
+
super('emailConfig', null, cacheManager, utilsService, EmailProviderConfigService.name, true), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "emailConfig", void 0), _define_property(this, "dataSourceProvider", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.emailConfig = emailConfig, this.dataSourceProvider = dataSourceProvider;
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
EmailProviderConfigService = _ts_decorate([
|
|
166
|
+
(0, _common.Injectable)({
|
|
167
|
+
scope: _common.Scope.REQUEST
|
|
168
|
+
}),
|
|
169
|
+
_ts_param(0, (0, _common.Inject)('CACHE_INSTANCE')),
|
|
170
|
+
_ts_param(1, (0, _common.Inject)(_modules.UtilsService)),
|
|
171
|
+
_ts_param(2, (0, _common.Inject)(_config.EmailConfigService)),
|
|
172
|
+
_ts_param(3, (0, _common.Inject)(_emaildatasourceprovider.EmailDataSourceProvider)),
|
|
173
|
+
_ts_metadata("design:type", Function),
|
|
174
|
+
_ts_metadata("design:paramtypes", [
|
|
175
|
+
typeof _classes.HybridCache === "undefined" ? Object : _classes.HybridCache,
|
|
176
|
+
typeof _modules.UtilsService === "undefined" ? Object : _modules.UtilsService,
|
|
177
|
+
typeof _config.EmailConfigService === "undefined" ? Object : _config.EmailConfigService,
|
|
178
|
+
typeof _emaildatasourceprovider.EmailDataSourceProvider === "undefined" ? Object : _emaildatasourceprovider.EmailDataSourceProvider
|
|
179
|
+
])
|
|
180
|
+
], EmailProviderConfigService);
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "EmailSendService", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return EmailSendService;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const _common = require("@nestjs/common");
|
|
12
|
+
const _config = require("../config");
|
|
13
|
+
const _providers = require("../providers");
|
|
14
|
+
const _emailproviderconfigservice = require("./email-provider-config.service");
|
|
15
|
+
const _emailtemplateservice = require("./email-template.service");
|
|
16
|
+
function _define_property(obj, key, value) {
|
|
17
|
+
if (key in obj) {
|
|
18
|
+
Object.defineProperty(obj, key, {
|
|
19
|
+
value: value,
|
|
20
|
+
enumerable: true,
|
|
21
|
+
configurable: true,
|
|
22
|
+
writable: true
|
|
23
|
+
});
|
|
24
|
+
} else {
|
|
25
|
+
obj[key] = value;
|
|
26
|
+
}
|
|
27
|
+
return obj;
|
|
28
|
+
}
|
|
29
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
30
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
31
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
32
|
+
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
33
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
34
|
+
}
|
|
35
|
+
function _ts_metadata(k, v) {
|
|
36
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
37
|
+
}
|
|
38
|
+
function _ts_param(paramIndex, decorator) {
|
|
39
|
+
return function(target, key) {
|
|
40
|
+
decorator(target, key, paramIndex);
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
let EmailSendService = class EmailSendService {
|
|
44
|
+
/**
|
|
45
|
+
* Get email provider based on config ID or default
|
|
46
|
+
*/ async getEmailProviderWithConfig(emailConfigId, user) {
|
|
47
|
+
let emailConfig;
|
|
48
|
+
if (emailConfigId) {
|
|
49
|
+
const config = await this.emailProviderConfigService.findByIdDirect(emailConfigId);
|
|
50
|
+
if (!config) {
|
|
51
|
+
throw new _common.NotFoundException('Email configuration not found');
|
|
52
|
+
}
|
|
53
|
+
// Validate company ownership
|
|
54
|
+
if (this.emailConfigService.isCompanyFeatureEnabled() && user?.companyId) {
|
|
55
|
+
const configWithCompany = config;
|
|
56
|
+
if (configWithCompany.companyId && configWithCompany.companyId !== user.companyId) {
|
|
57
|
+
throw new _common.BadRequestException('Email configuration belongs to another company');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (!config.isActive) {
|
|
61
|
+
throw new _common.BadRequestException('Email configuration is inactive');
|
|
62
|
+
}
|
|
63
|
+
emailConfig = config;
|
|
64
|
+
} else {
|
|
65
|
+
const defaultConfig = await this.emailProviderConfigService.getDefaultConfig(user);
|
|
66
|
+
if (!defaultConfig) {
|
|
67
|
+
throw new _common.NotFoundException('No default email configuration found. Please create one.');
|
|
68
|
+
}
|
|
69
|
+
emailConfig = defaultConfig;
|
|
70
|
+
}
|
|
71
|
+
const providerConfig = {
|
|
72
|
+
provider: emailConfig.provider,
|
|
73
|
+
config: emailConfig.config
|
|
74
|
+
};
|
|
75
|
+
const provider = await this.emailFactory.createProvider(providerConfig);
|
|
76
|
+
return {
|
|
77
|
+
provider,
|
|
78
|
+
config: emailConfig
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Interpolate variables in template content
|
|
83
|
+
* Replaces {{variableName}} with actual values
|
|
84
|
+
*/ interpolateVariables(content, variables) {
|
|
85
|
+
if (!variables || Object.keys(variables).length === 0) {
|
|
86
|
+
return content;
|
|
87
|
+
}
|
|
88
|
+
return content.replace(/\{\{(\w+)\}\}/g, (match, varName)=>{
|
|
89
|
+
return variables[varName] !== undefined ? String(variables[varName]) : match;
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Send email directly (without template)
|
|
94
|
+
*/ async sendEmail(dto, user) {
|
|
95
|
+
const { provider, config } = await this.getEmailProviderWithConfig(dto.emailConfigId, user);
|
|
96
|
+
const result = await provider.sendEmail({
|
|
97
|
+
to: dto.to,
|
|
98
|
+
cc: dto.cc,
|
|
99
|
+
bcc: dto.bcc,
|
|
100
|
+
subject: dto.subject,
|
|
101
|
+
html: dto.html,
|
|
102
|
+
text: dto.text,
|
|
103
|
+
from: dto.from || config.fromEmail || undefined,
|
|
104
|
+
fromName: dto.fromName || config.fromName || this.emailConfigService.getDefaultFromName(),
|
|
105
|
+
replyTo: dto.replyTo,
|
|
106
|
+
attachments: dto.attachments?.map((a)=>({
|
|
107
|
+
filename: a.filename,
|
|
108
|
+
content: Buffer.from(a.content, 'base64'),
|
|
109
|
+
contentType: a.contentType
|
|
110
|
+
}))
|
|
111
|
+
});
|
|
112
|
+
this.logger.log(`Email sent to ${Array.isArray(dto.to) ? dto.to.join(', ') : dto.to}: ${result.success ? 'SUCCESS' : 'FAILED'}`);
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Send email using template
|
|
117
|
+
*/ async sendTemplateEmail(dto, user) {
|
|
118
|
+
// Get template by ID or slug
|
|
119
|
+
let template;
|
|
120
|
+
if (dto.templateId) {
|
|
121
|
+
template = await this.emailTemplateService.findByIdDirect(dto.templateId);
|
|
122
|
+
} else if (dto.templateSlug) {
|
|
123
|
+
template = await this.emailTemplateService.findBySlug(dto.templateSlug, user);
|
|
124
|
+
} else {
|
|
125
|
+
throw new _common.BadRequestException('templateId or templateSlug is required');
|
|
126
|
+
}
|
|
127
|
+
if (!template) {
|
|
128
|
+
throw new _common.NotFoundException('Email template not found');
|
|
129
|
+
}
|
|
130
|
+
if (!template.isActive) {
|
|
131
|
+
throw new _common.BadRequestException('Email template is inactive');
|
|
132
|
+
}
|
|
133
|
+
// Validate company ownership
|
|
134
|
+
if (this.emailConfigService.isCompanyFeatureEnabled() && user?.companyId) {
|
|
135
|
+
const templateWithCompany = template;
|
|
136
|
+
if (templateWithCompany.companyId && templateWithCompany.companyId !== user.companyId) {
|
|
137
|
+
throw new _common.BadRequestException('Email template belongs to another company');
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// Interpolate variables in subject and content
|
|
141
|
+
const subject = this.interpolateVariables(template.subject, dto.variables || {});
|
|
142
|
+
// Check isHtml to determine which content to send
|
|
143
|
+
let html;
|
|
144
|
+
let text;
|
|
145
|
+
if (template.isHtml) {
|
|
146
|
+
// HTML template - send htmlContent, with textContent as fallback
|
|
147
|
+
html = this.interpolateVariables(template.htmlContent, dto.variables || {});
|
|
148
|
+
text = template.textContent ? this.interpolateVariables(template.textContent, dto.variables || {}) : undefined;
|
|
149
|
+
} else {
|
|
150
|
+
// Plain text template - send only textContent, no HTML
|
|
151
|
+
text = template.textContent ? this.interpolateVariables(template.textContent, dto.variables || {}) : this.interpolateVariables(template.htmlContent, dto.variables || {}); // fallback to htmlContent if textContent is empty
|
|
152
|
+
html = undefined;
|
|
153
|
+
}
|
|
154
|
+
// Get provider and send
|
|
155
|
+
const { provider, config } = await this.getEmailProviderWithConfig(dto.emailConfigId, user);
|
|
156
|
+
const result = await provider.sendEmail({
|
|
157
|
+
to: dto.to,
|
|
158
|
+
cc: dto.cc,
|
|
159
|
+
bcc: dto.bcc,
|
|
160
|
+
subject,
|
|
161
|
+
html,
|
|
162
|
+
text,
|
|
163
|
+
from: dto.from || config.fromEmail || undefined,
|
|
164
|
+
fromName: dto.fromName || config.fromName || this.emailConfigService.getDefaultFromName(),
|
|
165
|
+
replyTo: dto.replyTo,
|
|
166
|
+
attachments: dto.attachments?.map((a)=>({
|
|
167
|
+
filename: a.filename,
|
|
168
|
+
content: Buffer.from(a.content, 'base64'),
|
|
169
|
+
contentType: a.contentType
|
|
170
|
+
}))
|
|
171
|
+
});
|
|
172
|
+
this.logger.log(`Template email sent to ${Array.isArray(dto.to) ? dto.to.join(', ') : dto.to} using template "${template.slug}": ${result.success ? 'SUCCESS' : 'FAILED'}`);
|
|
173
|
+
return result;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Send test email (for testing configuration)
|
|
177
|
+
*/ async sendTestEmail(emailConfigId, recipient, user) {
|
|
178
|
+
const { provider, config } = await this.getEmailProviderWithConfig(emailConfigId, user);
|
|
179
|
+
const result = await provider.sendEmail({
|
|
180
|
+
to: recipient,
|
|
181
|
+
subject: 'Test Email from FLUSYS',
|
|
182
|
+
html: `
|
|
183
|
+
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
|
|
184
|
+
<h1 style="color: #333;">Test Email</h1>
|
|
185
|
+
<p>This is a test email from FLUSYS Email Service.</p>
|
|
186
|
+
<p><strong>Configuration:</strong> ${config.name}</p>
|
|
187
|
+
<p><strong>Provider:</strong> ${config.provider.toUpperCase()}</p>
|
|
188
|
+
<p style="color: #666; font-size: 12px;">
|
|
189
|
+
If you received this email, your email configuration is working correctly.
|
|
190
|
+
</p>
|
|
191
|
+
</div>
|
|
192
|
+
`,
|
|
193
|
+
text: `Test Email from FLUSYS\n\nThis is a test email.\nConfiguration: ${config.name}\nProvider: ${config.provider.toUpperCase()}`,
|
|
194
|
+
from: config.fromEmail || undefined,
|
|
195
|
+
fromName: config.fromName || this.emailConfigService.getDefaultFromName()
|
|
196
|
+
});
|
|
197
|
+
this.logger.log(`Test email sent to ${recipient}: ${result.success ? 'SUCCESS' : 'FAILED'}`);
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
200
|
+
constructor(emailFactory, emailConfigService, emailProviderConfigService, emailTemplateService){
|
|
201
|
+
_define_property(this, "emailFactory", void 0);
|
|
202
|
+
_define_property(this, "emailConfigService", void 0);
|
|
203
|
+
_define_property(this, "emailProviderConfigService", void 0);
|
|
204
|
+
_define_property(this, "emailTemplateService", void 0);
|
|
205
|
+
_define_property(this, "logger", void 0);
|
|
206
|
+
this.emailFactory = emailFactory;
|
|
207
|
+
this.emailConfigService = emailConfigService;
|
|
208
|
+
this.emailProviderConfigService = emailProviderConfigService;
|
|
209
|
+
this.emailTemplateService = emailTemplateService;
|
|
210
|
+
this.logger = new _common.Logger(EmailSendService.name);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
EmailSendService = _ts_decorate([
|
|
214
|
+
(0, _common.Injectable)({
|
|
215
|
+
scope: _common.Scope.REQUEST
|
|
216
|
+
}),
|
|
217
|
+
_ts_param(0, (0, _common.Inject)(_providers.EmailFactoryService)),
|
|
218
|
+
_ts_param(1, (0, _common.Inject)(_config.EmailConfigService)),
|
|
219
|
+
_ts_param(2, (0, _common.Inject)(_emailproviderconfigservice.EmailProviderConfigService)),
|
|
220
|
+
_ts_param(3, (0, _common.Inject)(_emailtemplateservice.EmailTemplateService)),
|
|
221
|
+
_ts_metadata("design:type", Function),
|
|
222
|
+
_ts_metadata("design:paramtypes", [
|
|
223
|
+
typeof _providers.EmailFactoryService === "undefined" ? Object : _providers.EmailFactoryService,
|
|
224
|
+
typeof _config.EmailConfigService === "undefined" ? Object : _config.EmailConfigService,
|
|
225
|
+
typeof _emailproviderconfigservice.EmailProviderConfigService === "undefined" ? Object : _emailproviderconfigservice.EmailProviderConfigService,
|
|
226
|
+
typeof _emailtemplateservice.EmailTemplateService === "undefined" ? Object : _emailtemplateservice.EmailTemplateService
|
|
227
|
+
])
|
|
228
|
+
], EmailSendService);
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "EmailTemplateService", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return EmailTemplateService;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const _classes = require("@flusys/nestjs-shared/classes");
|
|
12
|
+
const _modules = require("@flusys/nestjs-shared/modules");
|
|
13
|
+
const _common = require("@nestjs/common");
|
|
14
|
+
const _config = require("../config");
|
|
15
|
+
const _entities = require("../entities");
|
|
16
|
+
const _emaildatasourceprovider = require("./email-datasource.provider");
|
|
17
|
+
function _define_property(obj, key, value) {
|
|
18
|
+
if (key in obj) {
|
|
19
|
+
Object.defineProperty(obj, key, {
|
|
20
|
+
value: value,
|
|
21
|
+
enumerable: true,
|
|
22
|
+
configurable: true,
|
|
23
|
+
writable: true
|
|
24
|
+
});
|
|
25
|
+
} else {
|
|
26
|
+
obj[key] = value;
|
|
27
|
+
}
|
|
28
|
+
return obj;
|
|
29
|
+
}
|
|
30
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
31
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
32
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
33
|
+
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
34
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
35
|
+
}
|
|
36
|
+
function _ts_metadata(k, v) {
|
|
37
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
38
|
+
}
|
|
39
|
+
function _ts_param(paramIndex, decorator) {
|
|
40
|
+
return function(target, key) {
|
|
41
|
+
decorator(target, key, paramIndex);
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
let EmailTemplateService = class EmailTemplateService extends _classes.RequestScopedApiService {
|
|
45
|
+
/**
|
|
46
|
+
* Resolve entity class for this service
|
|
47
|
+
*/ resolveEntity() {
|
|
48
|
+
const enableCompanyFeature = this.emailConfig.isCompanyFeatureEnabled();
|
|
49
|
+
return enableCompanyFeature ? _entities.EmailTemplateWithCompany : _entities.EmailTemplate;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Get DataSource provider for this service
|
|
53
|
+
*/ getDataSourceProvider() {
|
|
54
|
+
return this.dataSourceProvider;
|
|
55
|
+
}
|
|
56
|
+
async convertSingleDtoToEntity(dto, user) {
|
|
57
|
+
// For updates, fetch existing template to handle versioning
|
|
58
|
+
const updateDto = dto;
|
|
59
|
+
if (updateDto.id) {
|
|
60
|
+
await this.ensureRepositoryInitialized();
|
|
61
|
+
const existing = await this.repository.findOne({
|
|
62
|
+
where: {
|
|
63
|
+
id: updateDto.id
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
if (existing) {
|
|
67
|
+
// Auto-increment schema version if schema changed
|
|
68
|
+
if (dto.schema && JSON.stringify(dto.schema) !== JSON.stringify(existing.schema)) {
|
|
69
|
+
dto.schemaVersion = existing.schemaVersion + 1;
|
|
70
|
+
this.logger.log(`Schema changed for template ${updateDto.id}, incrementing version from ${existing.schemaVersion} to ${dto.schemaVersion}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
let templateEntity = {
|
|
75
|
+
...dto
|
|
76
|
+
};
|
|
77
|
+
// Set company fields if company feature is enabled
|
|
78
|
+
if (this.emailConfig.isCompanyFeatureEnabled()) {
|
|
79
|
+
templateEntity.companyId = user?.companyId ?? null;
|
|
80
|
+
}
|
|
81
|
+
return templateEntity;
|
|
82
|
+
}
|
|
83
|
+
async getSelectQuery(query, _user, select) {
|
|
84
|
+
if (!select || !select.length) {
|
|
85
|
+
select = [
|
|
86
|
+
'id',
|
|
87
|
+
'name',
|
|
88
|
+
'slug',
|
|
89
|
+
'description',
|
|
90
|
+
'subject',
|
|
91
|
+
'schema',
|
|
92
|
+
'htmlContent',
|
|
93
|
+
'textContent',
|
|
94
|
+
'schemaVersion',
|
|
95
|
+
'isActive',
|
|
96
|
+
'isHtml',
|
|
97
|
+
'metadata',
|
|
98
|
+
'createdAt',
|
|
99
|
+
'updatedAt'
|
|
100
|
+
];
|
|
101
|
+
if (this.emailConfig.isCompanyFeatureEnabled()) {
|
|
102
|
+
select.push('companyId');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const selectFields = select.map((field)=>`${this.entityName}.${field}`);
|
|
106
|
+
query.select(selectFields);
|
|
107
|
+
return {
|
|
108
|
+
query,
|
|
109
|
+
isRaw: false
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Override: Extra query manipulation - Auto-filter by user's company
|
|
114
|
+
*/ async getExtraManipulateQuery(query, filterDto, user) {
|
|
115
|
+
const result = await super.getExtraManipulateQuery(query, filterDto, user);
|
|
116
|
+
const enableCompanyFeature = this.emailConfig.isCompanyFeatureEnabled();
|
|
117
|
+
if (enableCompanyFeature && user?.companyId) {
|
|
118
|
+
query.andWhere('emailTemplate.companyId = :companyId', {
|
|
119
|
+
companyId: user.companyId
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
query.orderBy(`${this.entityName}.createdAt`, 'DESC');
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Find template by ID directly (bypasses company filtering)
|
|
127
|
+
*/ async findByIdDirect(id) {
|
|
128
|
+
await this.ensureRepositoryInitialized();
|
|
129
|
+
return await this.repository.findOne({
|
|
130
|
+
where: {
|
|
131
|
+
id
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Find template by slug (scoped to user's company if enabled)
|
|
137
|
+
*/ async findBySlug(slug, user) {
|
|
138
|
+
await this.ensureRepositoryInitialized();
|
|
139
|
+
const where = {
|
|
140
|
+
slug,
|
|
141
|
+
isActive: true
|
|
142
|
+
};
|
|
143
|
+
if (this.emailConfig.isCompanyFeatureEnabled() && user?.companyId) {
|
|
144
|
+
where.companyId = user.companyId;
|
|
145
|
+
}
|
|
146
|
+
return await this.repository.findOne({
|
|
147
|
+
where
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Get all active templates (scoped to user's company if enabled)
|
|
152
|
+
*/ async getActiveTemplates(user) {
|
|
153
|
+
await this.ensureRepositoryInitialized();
|
|
154
|
+
const where = {
|
|
155
|
+
isActive: true
|
|
156
|
+
};
|
|
157
|
+
if (this.emailConfig.isCompanyFeatureEnabled() && user?.companyId) {
|
|
158
|
+
where.companyId = user.companyId;
|
|
159
|
+
}
|
|
160
|
+
return await this.repository.find({
|
|
161
|
+
where,
|
|
162
|
+
order: {
|
|
163
|
+
name: 'ASC'
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
constructor(cacheManager, utilsService, emailConfig, dataSourceProvider){
|
|
168
|
+
super('emailTemplate', null, cacheManager, utilsService, EmailTemplateService.name, true), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "emailConfig", void 0), _define_property(this, "dataSourceProvider", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.emailConfig = emailConfig, this.dataSourceProvider = dataSourceProvider;
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
EmailTemplateService = _ts_decorate([
|
|
172
|
+
(0, _common.Injectable)({
|
|
173
|
+
scope: _common.Scope.REQUEST
|
|
174
|
+
}),
|
|
175
|
+
_ts_param(0, (0, _common.Inject)('CACHE_INSTANCE')),
|
|
176
|
+
_ts_param(1, (0, _common.Inject)(_modules.UtilsService)),
|
|
177
|
+
_ts_param(2, (0, _common.Inject)(_config.EmailConfigService)),
|
|
178
|
+
_ts_param(3, (0, _common.Inject)(_emaildatasourceprovider.EmailDataSourceProvider)),
|
|
179
|
+
_ts_metadata("design:type", Function),
|
|
180
|
+
_ts_metadata("design:paramtypes", [
|
|
181
|
+
typeof _classes.HybridCache === "undefined" ? Object : _classes.HybridCache,
|
|
182
|
+
typeof _modules.UtilsService === "undefined" ? Object : _modules.UtilsService,
|
|
183
|
+
typeof _config.EmailConfigService === "undefined" ? Object : _config.EmailConfigService,
|
|
184
|
+
typeof _emaildatasourceprovider.EmailDataSourceProvider === "undefined" ? Object : _emaildatasourceprovider.EmailDataSourceProvider
|
|
185
|
+
])
|
|
186
|
+
], EmailTemplateService);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
_export_star(require("./email-datasource.provider"), exports);
|
|
6
|
+
_export_star(require("./email-provider-config.service"), exports);
|
|
7
|
+
_export_star(require("./email-template.service"), exports);
|
|
8
|
+
_export_star(require("./email-send.service"), exports);
|
|
9
|
+
function _export_star(from, to) {
|
|
10
|
+
Object.keys(from).forEach(function(k) {
|
|
11
|
+
if (k !== "default" && !Object.prototype.hasOwnProperty.call(to, k)) {
|
|
12
|
+
Object.defineProperty(to, k, {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
get: function() {
|
|
15
|
+
return from[k];
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
return from;
|
|
21
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { EmailModuleOptions } from '../interfaces';
|
|
2
|
+
export declare class EmailConfigService {
|
|
3
|
+
private readonly options;
|
|
4
|
+
constructor(options: EmailModuleOptions);
|
|
5
|
+
isCompanyFeatureEnabled(): boolean;
|
|
6
|
+
getDatabaseMode(): 'single' | 'multi-tenant';
|
|
7
|
+
getRateLimitPerMinute(): number;
|
|
8
|
+
isLoggingEnabled(): boolean;
|
|
9
|
+
getDefaultProvider(): string | undefined;
|
|
10
|
+
getDefaultFromName(): string;
|
|
11
|
+
getDefaultDatabaseConfig(): import("@flusys/nestjs-core").IDatabaseConfig;
|
|
12
|
+
getOptions(): EmailModuleOptions;
|
|
13
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const EMAIL_MODULE_OPTIONS = "EMAIL_MODULE_OPTIONS";
|
|
2
|
+
export declare const EMAIL_CONFIG_SERVICE = "EMAIL_CONFIG_SERVICE";
|
|
3
|
+
export declare const EMAIL_DATA_SOURCE_PROVIDER = "EMAIL_DATA_SOURCE_PROVIDER";
|
|
4
|
+
export declare const DEFAULT_FROM_NAME = "FLUSYS";
|
|
5
|
+
export declare const EMAIL_VALIDATION_MESSAGES: {
|
|
6
|
+
readonly INVALID_RECIPIENT: "Invalid email recipient";
|
|
7
|
+
readonly TEMPLATE_NOT_FOUND: "Email template not found";
|
|
8
|
+
readonly CONFIG_NOT_FOUND: "Email configuration not found";
|
|
9
|
+
readonly PROVIDER_NOT_AVAILABLE: "Email provider not available";
|
|
10
|
+
readonly SEND_FAILED: "Failed to send email";
|
|
11
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { CreateEmailConfigDto, EmailConfigResponseDto, UpdateEmailConfigDto } from '../dtos';
|
|
2
|
+
import { EmailProviderConfigService } from '../services';
|
|
3
|
+
declare const EmailConfigController_base: abstract new (service: EmailProviderConfigService) => {
|
|
4
|
+
service: EmailProviderConfigService;
|
|
5
|
+
insert(addDto: CreateEmailConfigDto, user: import("@flusys/nestjs-shared/interfaces").ILoggedUserInfo | null): Promise<import("@flusys/nestjs-shared/dtos").SingleResponseDto<EmailConfigResponseDto>>;
|
|
6
|
+
insertMany(addDto: CreateEmailConfigDto[], user: import("@flusys/nestjs-shared/interfaces").ILoggedUserInfo | null): Promise<import("@flusys/nestjs-shared/dtos").BulkResponseDto<EmailConfigResponseDto>>;
|
|
7
|
+
getById(id: string, body: import("@flusys/nestjs-shared/dtos").GetByIdBodyDto, user: import("@flusys/nestjs-shared/interfaces").ILoggedUserInfo | null): Promise<import("@flusys/nestjs-shared/dtos").SingleResponseDto<EmailConfigResponseDto>>;
|
|
8
|
+
update(updateDto: UpdateEmailConfigDto, user: import("@flusys/nestjs-shared/interfaces").ILoggedUserInfo | null): Promise<import("@flusys/nestjs-shared/dtos").SingleResponseDto<EmailConfigResponseDto>>;
|
|
9
|
+
updateMany(updateDtos: UpdateEmailConfigDto[], user: import("@flusys/nestjs-shared/interfaces").ILoggedUserInfo | null): Promise<import("@flusys/nestjs-shared/dtos").BulkResponseDto<EmailConfigResponseDto>>;
|
|
10
|
+
getAll(filterAndPaginationDto: import("@flusys/nestjs-shared/dtos").FilterAndPaginationDto, user: import("@flusys/nestjs-shared/interfaces").ILoggedUserInfo | null, search?: string): Promise<import("@flusys/nestjs-shared/dtos").ListResponseDto<EmailConfigResponseDto>>;
|
|
11
|
+
delete(deleteDto: import("@flusys/nestjs-shared/dtos").DeleteDto, user: import("@flusys/nestjs-shared/interfaces").ILoggedUserInfo | null): Promise<import("@flusys/nestjs-shared/dtos").MessageResponseDto>;
|
|
12
|
+
};
|
|
13
|
+
export declare class EmailConfigController extends EmailConfigController_base {
|
|
14
|
+
emailConfigService: EmailProviderConfigService;
|
|
15
|
+
constructor(emailConfigService: EmailProviderConfigService);
|
|
16
|
+
}
|
|
17
|
+
export {};
|