@flusys/nestjs-email 1.1.0-beta → 1.1.0
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/README.md +604 -0
- package/cjs/config/email.constants.js +0 -18
- package/cjs/config/index.js +0 -1
- package/cjs/controllers/email-config.controller.js +46 -4
- package/cjs/controllers/email-send.controller.js +13 -26
- package/cjs/controllers/email-template.controller.js +60 -11
- package/cjs/docs/email-swagger.config.js +18 -80
- package/cjs/dtos/email-config.dto.js +6 -106
- package/cjs/dtos/email-send.dto.js +101 -123
- package/cjs/dtos/email-template.dto.js +41 -103
- package/cjs/entities/email-config-with-company.entity.js +2 -2
- package/cjs/entities/email-config.entity.js +91 -3
- package/cjs/entities/email-template-with-company.entity.js +5 -3
- package/cjs/entities/email-template.entity.js +119 -3
- package/cjs/entities/index.js +34 -19
- package/cjs/index.js +1 -0
- package/cjs/interfaces/email-provider.interface.js +1 -3
- package/cjs/modules/email.module.js +50 -104
- package/cjs/providers/email-factory.service.js +37 -109
- package/cjs/providers/email-provider.registry.js +5 -15
- package/cjs/providers/mailgun-provider.js +54 -58
- package/cjs/providers/sendgrid-provider.js +68 -92
- package/cjs/providers/smtp-provider.js +58 -69
- package/cjs/{config → services}/email-config.service.js +9 -32
- package/cjs/services/email-datasource.provider.js +17 -104
- package/cjs/services/email-provider-config.service.js +28 -58
- package/cjs/services/email-send.service.js +120 -125
- package/cjs/services/email-template.service.js +62 -85
- package/cjs/services/index.js +2 -1
- package/cjs/utils/email-templates.util.js +64 -0
- package/cjs/utils/index.js +18 -0
- package/config/email.constants.d.ts +0 -9
- package/config/index.d.ts +0 -1
- package/controllers/email-send.controller.d.ts +5 -12
- package/controllers/email-template.controller.d.ts +5 -7
- package/dtos/email-config.dto.d.ts +5 -13
- package/dtos/email-send.dto.d.ts +17 -21
- package/dtos/email-template.dto.d.ts +5 -16
- package/entities/email-config-with-company.entity.d.ts +2 -2
- package/entities/email-config.entity.d.ts +9 -2
- package/entities/email-template-with-company.entity.d.ts +2 -2
- package/entities/email-template.entity.d.ts +13 -2
- package/entities/index.d.ts +9 -3
- package/fesm/config/email.constants.js +0 -9
- package/fesm/config/index.js +0 -1
- package/fesm/controllers/email-config.controller.js +49 -7
- package/fesm/controllers/email-send.controller.js +13 -26
- package/fesm/controllers/email-template.controller.js +61 -12
- package/fesm/docs/email-swagger.config.js +21 -86
- package/fesm/dtos/email-config.dto.js +9 -115
- package/fesm/dtos/email-send.dto.js +103 -139
- package/fesm/dtos/email-template.dto.js +43 -111
- package/fesm/entities/email-config-with-company.entity.js +2 -2
- package/fesm/entities/email-config.entity.js +92 -4
- package/fesm/entities/email-template-with-company.entity.js +5 -3
- package/fesm/entities/email-template.entity.js +120 -4
- package/fesm/entities/index.js +22 -16
- package/fesm/index.js +1 -0
- package/fesm/interfaces/email-config.interface.js +1 -3
- package/fesm/interfaces/email-module-options.interface.js +1 -3
- package/fesm/interfaces/email-provider.interface.js +1 -5
- package/fesm/interfaces/email-template.interface.js +1 -3
- package/fesm/modules/email.module.js +52 -106
- package/fesm/providers/email-factory.service.js +38 -69
- package/fesm/providers/email-provider.registry.js +6 -19
- package/fesm/providers/mailgun-provider.js +55 -63
- package/fesm/providers/sendgrid-provider.js +69 -97
- package/fesm/providers/smtp-provider.js +59 -73
- package/fesm/{config → services}/email-config.service.js +9 -32
- package/fesm/services/email-datasource.provider.js +18 -64
- package/fesm/services/email-provider-config.service.js +26 -56
- package/fesm/services/email-send.service.js +118 -123
- package/fesm/services/email-template.service.js +60 -83
- package/fesm/services/index.js +2 -1
- package/fesm/utils/email-templates.util.js +47 -0
- package/fesm/utils/index.js +1 -0
- package/index.d.ts +1 -0
- package/interfaces/email-config.interface.d.ts +6 -0
- package/interfaces/email-module-options.interface.d.ts +0 -5
- package/modules/email.module.d.ts +1 -2
- package/package.json +9 -4
- package/providers/email-factory.service.d.ts +4 -7
- package/providers/mailgun-provider.d.ts +6 -2
- package/providers/sendgrid-provider.d.ts +6 -2
- package/providers/smtp-provider.d.ts +7 -2
- package/services/email-config.service.d.ts +12 -0
- package/services/email-datasource.provider.d.ts +3 -6
- package/services/email-provider-config.service.d.ts +3 -3
- package/services/email-send.service.d.ts +11 -3
- package/services/email-template.service.d.ts +5 -4
- package/services/index.d.ts +2 -1
- package/utils/email-templates.util.d.ts +2 -0
- package/utils/index.d.ts +1 -0
- package/cjs/entities/email-config-base.entity.js +0 -111
- package/cjs/entities/email-template-base.entity.js +0 -134
- package/config/email-config.service.d.ts +0 -13
- package/entities/email-config-base.entity.d.ts +0 -11
- package/entities/email-template-base.entity.d.ts +0 -14
- package/fesm/entities/email-config-base.entity.js +0 -101
- package/fesm/entities/email-template-base.entity.js +0 -124
|
@@ -12,8 +12,8 @@ const _modules = require("@flusys/nestjs-shared/modules");
|
|
|
12
12
|
const _common = require("@nestjs/common");
|
|
13
13
|
const _core = require("@nestjs/core");
|
|
14
14
|
const _express = require("express");
|
|
15
|
-
const
|
|
16
|
-
const
|
|
15
|
+
const _entities = require("../entities");
|
|
16
|
+
const _emailconfigservice = require("./email-config.service");
|
|
17
17
|
function _define_property(obj, key, value) {
|
|
18
18
|
if (key in obj) {
|
|
19
19
|
Object.defineProperty(obj, key, {
|
|
@@ -27,47 +27,6 @@ function _define_property(obj, key, value) {
|
|
|
27
27
|
}
|
|
28
28
|
return obj;
|
|
29
29
|
}
|
|
30
|
-
function _getRequireWildcardCache(nodeInterop) {
|
|
31
|
-
if (typeof WeakMap !== "function") return null;
|
|
32
|
-
var cacheBabelInterop = new WeakMap();
|
|
33
|
-
var cacheNodeInterop = new WeakMap();
|
|
34
|
-
return (_getRequireWildcardCache = function(nodeInterop) {
|
|
35
|
-
return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
|
|
36
|
-
})(nodeInterop);
|
|
37
|
-
}
|
|
38
|
-
function _interop_require_wildcard(obj, nodeInterop) {
|
|
39
|
-
if (!nodeInterop && obj && obj.__esModule) {
|
|
40
|
-
return obj;
|
|
41
|
-
}
|
|
42
|
-
if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
|
|
43
|
-
return {
|
|
44
|
-
default: obj
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
var cache = _getRequireWildcardCache(nodeInterop);
|
|
48
|
-
if (cache && cache.has(obj)) {
|
|
49
|
-
return cache.get(obj);
|
|
50
|
-
}
|
|
51
|
-
var newObj = {
|
|
52
|
-
__proto__: null
|
|
53
|
-
};
|
|
54
|
-
var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
|
|
55
|
-
for(var key in obj){
|
|
56
|
-
if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
57
|
-
var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
|
|
58
|
-
if (desc && (desc.get || desc.set)) {
|
|
59
|
-
Object.defineProperty(newObj, key, desc);
|
|
60
|
-
} else {
|
|
61
|
-
newObj[key] = obj[key];
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
newObj.default = obj;
|
|
66
|
-
if (cache) {
|
|
67
|
-
cache.set(obj, newObj);
|
|
68
|
-
}
|
|
69
|
-
return newObj;
|
|
70
|
-
}
|
|
71
30
|
function _ts_decorate(decorators, target, key, desc) {
|
|
72
31
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
73
32
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -83,10 +42,8 @@ function _ts_param(paramIndex, decorator) {
|
|
|
83
42
|
};
|
|
84
43
|
}
|
|
85
44
|
let EmailDataSourceProvider = class EmailDataSourceProvider extends _modules.MultiTenantDataSourceService {
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
* Build parent options from EmailModuleOptions
|
|
89
|
-
*/ static buildParentOptions(options) {
|
|
45
|
+
// ─── Factory Methods ────────────────────────────────────────────────────────
|
|
46
|
+
static buildParentOptions(options) {
|
|
90
47
|
return {
|
|
91
48
|
bootstrapAppConfig: options.bootstrapAppConfig,
|
|
92
49
|
defaultDatabaseConfig: options.config?.defaultDatabaseConfig,
|
|
@@ -94,66 +51,28 @@ let EmailDataSourceProvider = class EmailDataSourceProvider extends _modules.Mul
|
|
|
94
51
|
tenants: options.config?.tenants
|
|
95
52
|
};
|
|
96
53
|
}
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
*/ getEnableCompanyFeature() {
|
|
101
|
-
return this.emailOptions.bootstrapAppConfig?.enableCompanyFeature ?? false;
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Get enable company feature for specific tenant
|
|
105
|
-
* Falls back to global setting if not specified per-tenant
|
|
106
|
-
*/ getEnableCompanyFeatureForTenant(tenant) {
|
|
107
|
-
return tenant?.enableCompanyFeature ?? this.getEnableCompanyFeature();
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Get enable company feature for current request context
|
|
111
|
-
*/ getEnableCompanyFeatureForCurrentTenant() {
|
|
112
|
-
return this.getEnableCompanyFeatureForTenant(this.getCurrentTenant() ?? undefined);
|
|
113
|
-
}
|
|
114
|
-
// ==================== Entity Management ====================
|
|
115
|
-
/**
|
|
116
|
-
* Get email entities for migrations based on company feature flag
|
|
117
|
-
*/ async getEmailEntities(enableCompanyFeature) {
|
|
118
|
-
const enable = enableCompanyFeature ?? this.getEnableCompanyFeature();
|
|
119
|
-
const { EmailConfig, EmailTemplate } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("../entities")));
|
|
120
|
-
if (enable) {
|
|
121
|
-
const { EmailConfigWithCompany, EmailTemplateWithCompany } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("../entities")));
|
|
122
|
-
return [
|
|
123
|
-
EmailConfigWithCompany,
|
|
124
|
-
EmailTemplateWithCompany
|
|
125
|
-
];
|
|
126
|
-
}
|
|
127
|
-
return [
|
|
128
|
-
EmailConfig,
|
|
129
|
-
EmailTemplate
|
|
130
|
-
];
|
|
54
|
+
// ─── Feature Flags ──────────────────────────────────────────────────────────
|
|
55
|
+
getEnableCompanyFeatureForTenant(tenant) {
|
|
56
|
+
return tenant?.enableCompanyFeature ?? this.configService.isCompanyFeatureEnabled();
|
|
131
57
|
}
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
* Override to dynamically set entities based on tenant config
|
|
135
|
-
*/ async createDataSourceFromConfig(config) {
|
|
58
|
+
// ─── Overrides ──────────────────────────────────────────────────────────────
|
|
59
|
+
async createDataSourceFromConfig(config) {
|
|
136
60
|
const currentTenant = this.getCurrentTenant();
|
|
137
61
|
const enableCompanyFeature = this.getEnableCompanyFeatureForTenant(currentTenant ?? undefined);
|
|
138
|
-
const entities =
|
|
62
|
+
const entities = (0, _entities.getEmailEntitiesByConfig)(enableCompanyFeature);
|
|
139
63
|
return super.createDataSourceFromConfig(config, entities);
|
|
140
64
|
}
|
|
141
|
-
|
|
142
|
-
* Override to use Email-specific static cache
|
|
143
|
-
*/ async getSingleDataSource() {
|
|
144
|
-
// Return existing initialized connection from Email-specific cache
|
|
65
|
+
async getSingleDataSource() {
|
|
145
66
|
if (EmailDataSourceProvider.singleDataSource?.isInitialized) {
|
|
146
67
|
return EmailDataSourceProvider.singleDataSource;
|
|
147
68
|
}
|
|
148
|
-
// If another request is creating the connection, wait for it
|
|
149
69
|
if (EmailDataSourceProvider.singleConnectionLock) {
|
|
150
70
|
return EmailDataSourceProvider.singleConnectionLock;
|
|
151
71
|
}
|
|
152
72
|
const config = this.getDefaultDatabaseConfig();
|
|
153
73
|
if (!config) {
|
|
154
|
-
throw new
|
|
74
|
+
throw new _common.InternalServerErrorException('No database config available. Provide defaultDatabaseConfig or tenantDefaultDatabaseConfig.');
|
|
155
75
|
}
|
|
156
|
-
// Create connection with lock to prevent race conditions
|
|
157
76
|
const connectionPromise = this.createDataSourceFromConfig(config);
|
|
158
77
|
EmailDataSourceProvider.singleConnectionLock = connectionPromise;
|
|
159
78
|
try {
|
|
@@ -164,20 +83,15 @@ let EmailDataSourceProvider = class EmailDataSourceProvider extends _modules.Mul
|
|
|
164
83
|
EmailDataSourceProvider.singleConnectionLock = null;
|
|
165
84
|
}
|
|
166
85
|
}
|
|
167
|
-
|
|
168
|
-
* Override to use Email-specific static cache for tenant connections
|
|
169
|
-
*/ async getOrCreateTenantConnection(tenant) {
|
|
170
|
-
// Return existing initialized connection from Email-specific cache
|
|
86
|
+
async getOrCreateTenantConnection(tenant) {
|
|
171
87
|
const existing = EmailDataSourceProvider.tenantConnections.get(tenant.id);
|
|
172
88
|
if (existing?.isInitialized) {
|
|
173
89
|
return existing;
|
|
174
90
|
}
|
|
175
|
-
// If another request is creating this tenant's connection, wait for it
|
|
176
91
|
const pendingConnection = EmailDataSourceProvider.connectionLocks.get(tenant.id);
|
|
177
92
|
if (pendingConnection) {
|
|
178
93
|
return pendingConnection;
|
|
179
94
|
}
|
|
180
|
-
// Create connection with lock to prevent race conditions
|
|
181
95
|
const config = this.buildTenantDatabaseConfig(tenant);
|
|
182
96
|
const connectionPromise = this.createDataSourceFromConfig(config);
|
|
183
97
|
EmailDataSourceProvider.connectionLocks.set(tenant.id, connectionPromise);
|
|
@@ -189,11 +103,10 @@ let EmailDataSourceProvider = class EmailDataSourceProvider extends _modules.Mul
|
|
|
189
103
|
EmailDataSourceProvider.connectionLocks.delete(tenant.id);
|
|
190
104
|
}
|
|
191
105
|
}
|
|
192
|
-
constructor(
|
|
193
|
-
super(EmailDataSourceProvider.buildParentOptions(
|
|
106
|
+
constructor(configService, request){
|
|
107
|
+
super(EmailDataSourceProvider.buildParentOptions(configService.getOptions()), request), _define_property(this, "configService", void 0), _define_property(this, "logger", void 0), this.configService = configService, this.logger = new _common.Logger(EmailDataSourceProvider.name);
|
|
194
108
|
}
|
|
195
109
|
};
|
|
196
|
-
// Override parent's static properties to have Email-specific cache
|
|
197
110
|
_define_property(EmailDataSourceProvider, "tenantConnections", new Map());
|
|
198
111
|
_define_property(EmailDataSourceProvider, "singleDataSource", null);
|
|
199
112
|
_define_property(EmailDataSourceProvider, "tenantsRegistry", new Map());
|
|
@@ -204,12 +117,12 @@ EmailDataSourceProvider = _ts_decorate([
|
|
|
204
117
|
(0, _common.Injectable)({
|
|
205
118
|
scope: _common.Scope.REQUEST
|
|
206
119
|
}),
|
|
207
|
-
_ts_param(0, (0, _common.Inject)(
|
|
120
|
+
_ts_param(0, (0, _common.Inject)(_emailconfigservice.EmailConfigService)),
|
|
208
121
|
_ts_param(1, (0, _common.Optional)()),
|
|
209
122
|
_ts_param(1, (0, _common.Inject)(_core.REQUEST)),
|
|
210
123
|
_ts_metadata("design:type", Function),
|
|
211
124
|
_ts_metadata("design:paramtypes", [
|
|
212
|
-
typeof
|
|
125
|
+
typeof _emailconfigservice.EmailConfigService === "undefined" ? Object : _emailconfigservice.EmailConfigService,
|
|
213
126
|
typeof _express.Request === "undefined" ? Object : _express.Request
|
|
214
127
|
])
|
|
215
128
|
], EmailDataSourceProvider);
|
|
@@ -10,8 +10,9 @@ Object.defineProperty(exports, "EmailProviderConfigService", {
|
|
|
10
10
|
});
|
|
11
11
|
const _classes = require("@flusys/nestjs-shared/classes");
|
|
12
12
|
const _modules = require("@flusys/nestjs-shared/modules");
|
|
13
|
+
const _utils = require("@flusys/nestjs-shared/utils");
|
|
13
14
|
const _common = require("@nestjs/common");
|
|
14
|
-
const
|
|
15
|
+
const _emailconfigservice = require("./email-config.service");
|
|
15
16
|
const _entities = require("../entities");
|
|
16
17
|
const _emaildatasourceprovider = require("./email-datasource.provider");
|
|
17
18
|
function _define_property(obj, key, value) {
|
|
@@ -42,29 +43,23 @@ function _ts_param(paramIndex, decorator) {
|
|
|
42
43
|
};
|
|
43
44
|
}
|
|
44
45
|
let EmailProviderConfigService = class EmailProviderConfigService extends _classes.RequestScopedApiService {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
*/ resolveEntity() {
|
|
48
|
-
const enableCompanyFeature = this.emailConfig.isCompanyFeatureEnabled();
|
|
49
|
-
return enableCompanyFeature ? _entities.EmailConfigWithCompany : _entities.EmailConfig;
|
|
46
|
+
resolveEntity() {
|
|
47
|
+
return this.emailConfig.isCompanyFeatureEnabled() ? _entities.EmailConfigWithCompany : _entities.EmailConfig;
|
|
50
48
|
}
|
|
51
|
-
|
|
52
|
-
* Get DataSource provider for this service
|
|
53
|
-
*/ getDataSourceProvider() {
|
|
49
|
+
getDataSourceProvider() {
|
|
54
50
|
return this.dataSourceProvider;
|
|
55
51
|
}
|
|
56
52
|
async convertSingleDtoToEntity(dto, user) {
|
|
57
|
-
|
|
53
|
+
const entity = {
|
|
58
54
|
...dto
|
|
59
55
|
};
|
|
60
|
-
// Set company fields if company feature is enabled
|
|
61
56
|
if (this.emailConfig.isCompanyFeatureEnabled()) {
|
|
62
|
-
|
|
57
|
+
entity.companyId = user?.companyId ?? null;
|
|
63
58
|
}
|
|
64
|
-
return
|
|
59
|
+
return entity;
|
|
65
60
|
}
|
|
66
61
|
async getSelectQuery(query, _user, select) {
|
|
67
|
-
if (!select
|
|
62
|
+
if (!select?.length) {
|
|
68
63
|
select = [
|
|
69
64
|
'id',
|
|
70
65
|
'name',
|
|
@@ -77,52 +72,36 @@ let EmailProviderConfigService = class EmailProviderConfigService extends _class
|
|
|
77
72
|
'createdAt',
|
|
78
73
|
'updatedAt'
|
|
79
74
|
];
|
|
80
|
-
if (this.emailConfig.isCompanyFeatureEnabled())
|
|
81
|
-
select.push('companyId');
|
|
82
|
-
}
|
|
75
|
+
if (this.emailConfig.isCompanyFeatureEnabled()) select.push('companyId');
|
|
83
76
|
}
|
|
84
|
-
|
|
85
|
-
query.select(selectFields);
|
|
77
|
+
query.select(select.map((field)=>`${this.entityName}.${field}`));
|
|
86
78
|
return {
|
|
87
79
|
query,
|
|
88
80
|
isRaw: false
|
|
89
81
|
};
|
|
90
82
|
}
|
|
91
|
-
|
|
92
|
-
* Override: Extra query manipulation - Auto-filter by user's company
|
|
93
|
-
*/ async getExtraManipulateQuery(query, filterDto, user) {
|
|
83
|
+
async getExtraManipulateQuery(query, filterDto, user) {
|
|
94
84
|
const result = await super.getExtraManipulateQuery(query, filterDto, user);
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
});
|
|
100
|
-
}
|
|
85
|
+
(0, _utils.applyCompanyFilter)(query, {
|
|
86
|
+
isCompanyFeatureEnabled: this.emailConfig.isCompanyFeatureEnabled(),
|
|
87
|
+
entityAlias: 'emailConfig'
|
|
88
|
+
}, user);
|
|
101
89
|
query.orderBy(`${this.entityName}.createdAt`, 'DESC');
|
|
102
90
|
return result;
|
|
103
91
|
}
|
|
104
|
-
|
|
105
|
-
* Find config by ID directly (bypasses company filtering)
|
|
106
|
-
*/ async findByIdDirect(id) {
|
|
92
|
+
async findByIdDirect(id) {
|
|
107
93
|
await this.ensureRepositoryInitialized();
|
|
108
|
-
return
|
|
94
|
+
return this.repository.findOne({
|
|
109
95
|
where: {
|
|
110
96
|
id
|
|
111
97
|
}
|
|
112
98
|
});
|
|
113
99
|
}
|
|
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) {
|
|
100
|
+
async getDefaultConfig(user) {
|
|
118
101
|
await this.ensureRepositoryInitialized();
|
|
119
|
-
const baseWhere = {
|
|
102
|
+
const baseWhere = (0, _utils.buildCompanyWhereCondition)({
|
|
120
103
|
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
|
|
104
|
+
}, this.emailConfig.isCompanyFeatureEnabled(), user);
|
|
126
105
|
const defaultConfig = await this.repository.findOne({
|
|
127
106
|
where: {
|
|
128
107
|
...baseWhere,
|
|
@@ -132,29 +111,20 @@ let EmailProviderConfigService = class EmailProviderConfigService extends _class
|
|
|
132
111
|
createdAt: 'ASC'
|
|
133
112
|
}
|
|
134
113
|
});
|
|
135
|
-
|
|
136
|
-
return defaultConfig;
|
|
137
|
-
}
|
|
138
|
-
// Fall back to any available active config (oldest first for consistency)
|
|
139
|
-
return await this.repository.findOne({
|
|
114
|
+
return defaultConfig || this.repository.findOne({
|
|
140
115
|
where: baseWhere,
|
|
141
116
|
order: {
|
|
142
117
|
createdAt: 'ASC'
|
|
143
118
|
}
|
|
144
119
|
});
|
|
145
120
|
}
|
|
146
|
-
|
|
147
|
-
* Get config by provider type
|
|
148
|
-
*/ async getConfigByProvider(provider, user) {
|
|
121
|
+
async getConfigByProvider(provider, user) {
|
|
149
122
|
await this.ensureRepositoryInitialized();
|
|
150
|
-
const where = {
|
|
123
|
+
const where = (0, _utils.buildCompanyWhereCondition)({
|
|
151
124
|
provider,
|
|
152
125
|
isActive: true
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
where.companyId = user.companyId;
|
|
156
|
-
}
|
|
157
|
-
return await this.repository.find({
|
|
126
|
+
}, this.emailConfig.isCompanyFeatureEnabled(), user);
|
|
127
|
+
return this.repository.find({
|
|
158
128
|
where
|
|
159
129
|
});
|
|
160
130
|
}
|
|
@@ -168,13 +138,13 @@ EmailProviderConfigService = _ts_decorate([
|
|
|
168
138
|
}),
|
|
169
139
|
_ts_param(0, (0, _common.Inject)('CACHE_INSTANCE')),
|
|
170
140
|
_ts_param(1, (0, _common.Inject)(_modules.UtilsService)),
|
|
171
|
-
_ts_param(2, (0, _common.Inject)(
|
|
141
|
+
_ts_param(2, (0, _common.Inject)(_emailconfigservice.EmailConfigService)),
|
|
172
142
|
_ts_param(3, (0, _common.Inject)(_emaildatasourceprovider.EmailDataSourceProvider)),
|
|
173
143
|
_ts_metadata("design:type", Function),
|
|
174
144
|
_ts_metadata("design:paramtypes", [
|
|
175
145
|
typeof _classes.HybridCache === "undefined" ? Object : _classes.HybridCache,
|
|
176
146
|
typeof _modules.UtilsService === "undefined" ? Object : _modules.UtilsService,
|
|
177
|
-
typeof
|
|
147
|
+
typeof _emailconfigservice.EmailConfigService === "undefined" ? Object : _emailconfigservice.EmailConfigService,
|
|
178
148
|
typeof _emaildatasourceprovider.EmailDataSourceProvider === "undefined" ? Object : _emaildatasourceprovider.EmailDataSourceProvider
|
|
179
149
|
])
|
|
180
150
|
], EmailProviderConfigService);
|
|
@@ -8,8 +8,9 @@ Object.defineProperty(exports, "EmailSendService", {
|
|
|
8
8
|
return EmailSendService;
|
|
9
9
|
}
|
|
10
10
|
});
|
|
11
|
+
const _utils = require("@flusys/nestjs-shared/utils");
|
|
11
12
|
const _common = require("@nestjs/common");
|
|
12
|
-
const
|
|
13
|
+
const _emailconfigservice = require("./email-config.service");
|
|
13
14
|
const _providers = require("../providers");
|
|
14
15
|
const _emailproviderconfigservice = require("./email-provider-config.service");
|
|
15
16
|
const _emailtemplateservice = require("./email-template.service");
|
|
@@ -40,59 +41,12 @@ function _ts_param(paramIndex, decorator) {
|
|
|
40
41
|
decorator(target, key, paramIndex);
|
|
41
42
|
};
|
|
42
43
|
}
|
|
44
|
+
/** Valid identifier pattern for template variables: {{varName}} */ const VARIABLE_PATTERN = /\{\{([a-zA-Z_][a-zA-Z0-9_]*)\}\}/g;
|
|
43
45
|
let EmailSendService = class EmailSendService {
|
|
44
|
-
|
|
45
|
-
|
|
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) {
|
|
46
|
+
// ─── Public API ─────────────────────────────────────────────────────────────
|
|
47
|
+
async sendEmail(dto, user) {
|
|
95
48
|
const { provider, config } = await this.getEmailProviderWithConfig(dto.emailConfigId, user);
|
|
49
|
+
const sender = this.resolveSender(dto, config);
|
|
96
50
|
const result = await provider.sendEmail({
|
|
97
51
|
to: dto.to,
|
|
98
52
|
cc: dto.cc,
|
|
@@ -100,59 +54,18 @@ let EmailSendService = class EmailSendService {
|
|
|
100
54
|
subject: dto.subject,
|
|
101
55
|
html: dto.html,
|
|
102
56
|
text: dto.text,
|
|
103
|
-
|
|
104
|
-
fromName: dto.fromName || config.fromName || this.emailConfigService.getDefaultFromName(),
|
|
57
|
+
...sender,
|
|
105
58
|
replyTo: dto.replyTo,
|
|
106
|
-
attachments: dto.attachments
|
|
107
|
-
filename: a.filename,
|
|
108
|
-
content: Buffer.from(a.content, 'base64'),
|
|
109
|
-
contentType: a.contentType
|
|
110
|
-
}))
|
|
59
|
+
attachments: this.buildAttachments(dto.attachments)
|
|
111
60
|
});
|
|
112
|
-
this.
|
|
61
|
+
this.logResult(dto.to, result);
|
|
113
62
|
return result;
|
|
114
63
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
|
64
|
+
async sendTemplateEmail(dto, user) {
|
|
65
|
+
const template = await this.resolveTemplate(dto, user);
|
|
66
|
+
const { subject, html, text } = this.buildTemplateContent(template, dto.variables);
|
|
155
67
|
const { provider, config } = await this.getEmailProviderWithConfig(dto.emailConfigId, user);
|
|
68
|
+
const sender = this.resolveSender(dto, config);
|
|
156
69
|
const result = await provider.sendEmail({
|
|
157
70
|
to: dto.to,
|
|
158
71
|
cc: dto.cc,
|
|
@@ -160,43 +73,125 @@ let EmailSendService = class EmailSendService {
|
|
|
160
73
|
subject,
|
|
161
74
|
html,
|
|
162
75
|
text,
|
|
163
|
-
|
|
164
|
-
fromName: dto.fromName || config.fromName || this.emailConfigService.getDefaultFromName(),
|
|
76
|
+
...sender,
|
|
165
77
|
replyTo: dto.replyTo,
|
|
166
|
-
attachments: dto.attachments
|
|
167
|
-
filename: a.filename,
|
|
168
|
-
content: Buffer.from(a.content, 'base64'),
|
|
169
|
-
contentType: a.contentType
|
|
170
|
-
}))
|
|
78
|
+
attachments: this.buildAttachments(dto.attachments)
|
|
171
79
|
});
|
|
172
|
-
this.
|
|
80
|
+
this.logResult(dto.to, result, template.slug);
|
|
173
81
|
return result;
|
|
174
82
|
}
|
|
175
|
-
|
|
176
|
-
* Send test email (for testing configuration)
|
|
177
|
-
*/ async sendTestEmail(emailConfigId, recipient, user) {
|
|
83
|
+
async sendTestEmail(emailConfigId, recipient, user) {
|
|
178
84
|
const { provider, config } = await this.getEmailProviderWithConfig(emailConfigId, user);
|
|
85
|
+
const safeConfig = (0, _utils.escapeHtmlVariables)({
|
|
86
|
+
name: config.name,
|
|
87
|
+
provider: config.provider.toUpperCase()
|
|
88
|
+
});
|
|
179
89
|
const result = await provider.sendEmail({
|
|
180
90
|
to: recipient,
|
|
181
91
|
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
|
-
`,
|
|
92
|
+
html: this.buildTestEmailHtml(safeConfig),
|
|
193
93
|
text: `Test Email from FLUSYS\n\nThis is a test email.\nConfiguration: ${config.name}\nProvider: ${config.provider.toUpperCase()}`,
|
|
194
94
|
from: config.fromEmail || undefined,
|
|
195
95
|
fromName: config.fromName || this.emailConfigService.getDefaultFromName()
|
|
196
96
|
});
|
|
197
|
-
this.
|
|
97
|
+
this.logResult(recipient, result);
|
|
198
98
|
return result;
|
|
199
99
|
}
|
|
100
|
+
// ─── Private: Provider & Config Resolution ──────────────────────────────────
|
|
101
|
+
async getEmailProviderWithConfig(emailConfigId, user) {
|
|
102
|
+
const emailConfig = emailConfigId ? await this.resolveConfigById(emailConfigId, user) : await this.resolveDefaultConfig(user);
|
|
103
|
+
const providerConfig = {
|
|
104
|
+
provider: emailConfig.provider,
|
|
105
|
+
config: emailConfig.config
|
|
106
|
+
};
|
|
107
|
+
return {
|
|
108
|
+
provider: await this.emailFactory.createProvider(providerConfig),
|
|
109
|
+
config: emailConfig
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
async resolveConfigById(id, user) {
|
|
113
|
+
const config = await this.emailProviderConfigService.findByIdDirect(id);
|
|
114
|
+
if (!config) throw new _common.NotFoundException('Email configuration not found');
|
|
115
|
+
(0, _utils.validateCompanyOwnership)(config, user, this.emailConfigService.isCompanyFeatureEnabled(), 'Email configuration');
|
|
116
|
+
if (!config.isActive) throw new _common.BadRequestException('Email configuration is inactive');
|
|
117
|
+
return config;
|
|
118
|
+
}
|
|
119
|
+
async resolveDefaultConfig(user) {
|
|
120
|
+
const config = await this.emailProviderConfigService.getDefaultConfig(user);
|
|
121
|
+
if (!config) throw new _common.NotFoundException('No default email configuration found');
|
|
122
|
+
return config;
|
|
123
|
+
}
|
|
124
|
+
// ─── Private: Template Resolution ───────────────────────────────────────────
|
|
125
|
+
async resolveTemplate(dto, user) {
|
|
126
|
+
let template = null;
|
|
127
|
+
if (dto.templateId) {
|
|
128
|
+
template = await this.emailTemplateService.findByIdDirect(dto.templateId);
|
|
129
|
+
} else if (dto.templateSlug) {
|
|
130
|
+
template = await this.emailTemplateService.findBySlug(dto.templateSlug, user);
|
|
131
|
+
} else {
|
|
132
|
+
throw new _common.BadRequestException('templateId or templateSlug is required');
|
|
133
|
+
}
|
|
134
|
+
if (!template) throw new _common.NotFoundException('Email template not found');
|
|
135
|
+
if (!template.isActive) throw new _common.BadRequestException('Email template is inactive');
|
|
136
|
+
(0, _utils.validateCompanyOwnership)(template, user, this.emailConfigService.isCompanyFeatureEnabled(), 'Email template');
|
|
137
|
+
return template;
|
|
138
|
+
}
|
|
139
|
+
buildTemplateContent(template, variables) {
|
|
140
|
+
const vars = variables || {};
|
|
141
|
+
const subject = this.interpolateVariables(template.subject, vars, false);
|
|
142
|
+
if (template.isHtml) {
|
|
143
|
+
return {
|
|
144
|
+
subject,
|
|
145
|
+
html: this.interpolateVariables(template.htmlContent, vars, true),
|
|
146
|
+
text: template.textContent ? this.interpolateVariables(template.textContent, vars, false) : undefined
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
subject,
|
|
151
|
+
html: undefined,
|
|
152
|
+
text: template.textContent ? this.interpolateVariables(template.textContent, vars, false) : this.interpolateVariables(template.htmlContent, vars, false)
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
// ─── Private: Helpers ───────────────────────────────────────────────────────
|
|
156
|
+
resolveSender(dto, config) {
|
|
157
|
+
return {
|
|
158
|
+
from: dto.from || config.fromEmail || undefined,
|
|
159
|
+
fromName: dto.fromName || config.fromName || this.emailConfigService.getDefaultFromName()
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
buildAttachments(attachments) {
|
|
163
|
+
return attachments?.map((a)=>({
|
|
164
|
+
filename: a.filename,
|
|
165
|
+
content: Buffer.from(a.content, 'base64'),
|
|
166
|
+
contentType: a.contentType
|
|
167
|
+
}));
|
|
168
|
+
}
|
|
169
|
+
interpolateVariables(content, variables, escapeHtml) {
|
|
170
|
+
if (!variables || Object.keys(variables).length === 0) return content;
|
|
171
|
+
const safeVars = escapeHtml ? (0, _utils.escapeHtmlVariables)(variables) : variables;
|
|
172
|
+
return content.replace(VARIABLE_PATTERN, (match, varName)=>{
|
|
173
|
+
return varName in safeVars && safeVars[varName] !== undefined ? String(safeVars[varName]) : match;
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
logResult(to, result, templateSlug) {
|
|
177
|
+
const recipient = Array.isArray(to) ? to.join(', ') : to;
|
|
178
|
+
const status = result.success ? 'SUCCESS' : 'FAILED';
|
|
179
|
+
const message = templateSlug ? `Template email sent to ${recipient} using template "${templateSlug}": ${status}` : `Email sent to ${recipient}: ${status}`;
|
|
180
|
+
this.logger.log(message);
|
|
181
|
+
}
|
|
182
|
+
buildTestEmailHtml(safeConfig) {
|
|
183
|
+
return `
|
|
184
|
+
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
|
|
185
|
+
<h1 style="color: #333;">Test Email</h1>
|
|
186
|
+
<p>This is a test email from FLUSYS Email Service.</p>
|
|
187
|
+
<p><strong>Configuration:</strong> ${safeConfig.name}</p>
|
|
188
|
+
<p><strong>Provider:</strong> ${safeConfig.provider}</p>
|
|
189
|
+
<p style="color: #666; font-size: 12px;">
|
|
190
|
+
If you received this email, your email configuration is working correctly.
|
|
191
|
+
</p>
|
|
192
|
+
</div>
|
|
193
|
+
`;
|
|
194
|
+
}
|
|
200
195
|
constructor(emailFactory, emailConfigService, emailProviderConfigService, emailTemplateService){
|
|
201
196
|
_define_property(this, "emailFactory", void 0);
|
|
202
197
|
_define_property(this, "emailConfigService", void 0);
|
|
@@ -215,13 +210,13 @@ EmailSendService = _ts_decorate([
|
|
|
215
210
|
scope: _common.Scope.REQUEST
|
|
216
211
|
}),
|
|
217
212
|
_ts_param(0, (0, _common.Inject)(_providers.EmailFactoryService)),
|
|
218
|
-
_ts_param(1, (0, _common.Inject)(
|
|
213
|
+
_ts_param(1, (0, _common.Inject)(_emailconfigservice.EmailConfigService)),
|
|
219
214
|
_ts_param(2, (0, _common.Inject)(_emailproviderconfigservice.EmailProviderConfigService)),
|
|
220
215
|
_ts_param(3, (0, _common.Inject)(_emailtemplateservice.EmailTemplateService)),
|
|
221
216
|
_ts_metadata("design:type", Function),
|
|
222
217
|
_ts_metadata("design:paramtypes", [
|
|
223
218
|
typeof _providers.EmailFactoryService === "undefined" ? Object : _providers.EmailFactoryService,
|
|
224
|
-
typeof
|
|
219
|
+
typeof _emailconfigservice.EmailConfigService === "undefined" ? Object : _emailconfigservice.EmailConfigService,
|
|
225
220
|
typeof _emailproviderconfigservice.EmailProviderConfigService === "undefined" ? Object : _emailproviderconfigservice.EmailProviderConfigService,
|
|
226
221
|
typeof _emailtemplateservice.EmailTemplateService === "undefined" ? Object : _emailtemplateservice.EmailTemplateService
|
|
227
222
|
])
|