@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.
Files changed (100) hide show
  1. package/README.md +604 -0
  2. package/cjs/config/email.constants.js +0 -18
  3. package/cjs/config/index.js +0 -1
  4. package/cjs/controllers/email-config.controller.js +46 -4
  5. package/cjs/controllers/email-send.controller.js +13 -26
  6. package/cjs/controllers/email-template.controller.js +60 -11
  7. package/cjs/docs/email-swagger.config.js +18 -80
  8. package/cjs/dtos/email-config.dto.js +6 -106
  9. package/cjs/dtos/email-send.dto.js +101 -123
  10. package/cjs/dtos/email-template.dto.js +41 -103
  11. package/cjs/entities/email-config-with-company.entity.js +2 -2
  12. package/cjs/entities/email-config.entity.js +91 -3
  13. package/cjs/entities/email-template-with-company.entity.js +5 -3
  14. package/cjs/entities/email-template.entity.js +119 -3
  15. package/cjs/entities/index.js +34 -19
  16. package/cjs/index.js +1 -0
  17. package/cjs/interfaces/email-provider.interface.js +1 -3
  18. package/cjs/modules/email.module.js +50 -104
  19. package/cjs/providers/email-factory.service.js +37 -109
  20. package/cjs/providers/email-provider.registry.js +5 -15
  21. package/cjs/providers/mailgun-provider.js +54 -58
  22. package/cjs/providers/sendgrid-provider.js +68 -92
  23. package/cjs/providers/smtp-provider.js +58 -69
  24. package/cjs/{config → services}/email-config.service.js +9 -32
  25. package/cjs/services/email-datasource.provider.js +17 -104
  26. package/cjs/services/email-provider-config.service.js +28 -58
  27. package/cjs/services/email-send.service.js +120 -125
  28. package/cjs/services/email-template.service.js +62 -85
  29. package/cjs/services/index.js +2 -1
  30. package/cjs/utils/email-templates.util.js +64 -0
  31. package/cjs/utils/index.js +18 -0
  32. package/config/email.constants.d.ts +0 -9
  33. package/config/index.d.ts +0 -1
  34. package/controllers/email-send.controller.d.ts +5 -12
  35. package/controllers/email-template.controller.d.ts +5 -7
  36. package/dtos/email-config.dto.d.ts +5 -13
  37. package/dtos/email-send.dto.d.ts +17 -21
  38. package/dtos/email-template.dto.d.ts +5 -16
  39. package/entities/email-config-with-company.entity.d.ts +2 -2
  40. package/entities/email-config.entity.d.ts +9 -2
  41. package/entities/email-template-with-company.entity.d.ts +2 -2
  42. package/entities/email-template.entity.d.ts +13 -2
  43. package/entities/index.d.ts +9 -3
  44. package/fesm/config/email.constants.js +0 -9
  45. package/fesm/config/index.js +0 -1
  46. package/fesm/controllers/email-config.controller.js +49 -7
  47. package/fesm/controllers/email-send.controller.js +13 -26
  48. package/fesm/controllers/email-template.controller.js +61 -12
  49. package/fesm/docs/email-swagger.config.js +21 -86
  50. package/fesm/dtos/email-config.dto.js +9 -115
  51. package/fesm/dtos/email-send.dto.js +103 -139
  52. package/fesm/dtos/email-template.dto.js +43 -111
  53. package/fesm/entities/email-config-with-company.entity.js +2 -2
  54. package/fesm/entities/email-config.entity.js +92 -4
  55. package/fesm/entities/email-template-with-company.entity.js +5 -3
  56. package/fesm/entities/email-template.entity.js +120 -4
  57. package/fesm/entities/index.js +22 -16
  58. package/fesm/index.js +1 -0
  59. package/fesm/interfaces/email-config.interface.js +1 -3
  60. package/fesm/interfaces/email-module-options.interface.js +1 -3
  61. package/fesm/interfaces/email-provider.interface.js +1 -5
  62. package/fesm/interfaces/email-template.interface.js +1 -3
  63. package/fesm/modules/email.module.js +52 -106
  64. package/fesm/providers/email-factory.service.js +38 -69
  65. package/fesm/providers/email-provider.registry.js +6 -19
  66. package/fesm/providers/mailgun-provider.js +55 -63
  67. package/fesm/providers/sendgrid-provider.js +69 -97
  68. package/fesm/providers/smtp-provider.js +59 -73
  69. package/fesm/{config → services}/email-config.service.js +9 -32
  70. package/fesm/services/email-datasource.provider.js +18 -64
  71. package/fesm/services/email-provider-config.service.js +26 -56
  72. package/fesm/services/email-send.service.js +118 -123
  73. package/fesm/services/email-template.service.js +60 -83
  74. package/fesm/services/index.js +2 -1
  75. package/fesm/utils/email-templates.util.js +47 -0
  76. package/fesm/utils/index.js +1 -0
  77. package/index.d.ts +1 -0
  78. package/interfaces/email-config.interface.d.ts +6 -0
  79. package/interfaces/email-module-options.interface.d.ts +0 -5
  80. package/modules/email.module.d.ts +1 -2
  81. package/package.json +9 -4
  82. package/providers/email-factory.service.d.ts +4 -7
  83. package/providers/mailgun-provider.d.ts +6 -2
  84. package/providers/sendgrid-provider.d.ts +6 -2
  85. package/providers/smtp-provider.d.ts +7 -2
  86. package/services/email-config.service.d.ts +12 -0
  87. package/services/email-datasource.provider.d.ts +3 -6
  88. package/services/email-provider-config.service.d.ts +3 -3
  89. package/services/email-send.service.d.ts +11 -3
  90. package/services/email-template.service.d.ts +5 -4
  91. package/services/index.d.ts +2 -1
  92. package/utils/email-templates.util.d.ts +2 -0
  93. package/utils/index.d.ts +1 -0
  94. package/cjs/entities/email-config-base.entity.js +0 -111
  95. package/cjs/entities/email-template-base.entity.js +0 -134
  96. package/config/email-config.service.d.ts +0 -13
  97. package/entities/email-config-base.entity.d.ts +0 -11
  98. package/entities/email-template-base.entity.d.ts +0 -14
  99. package/fesm/entities/email-config-base.entity.js +0 -101
  100. 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 _interfaces = require("../interfaces");
16
- const _emailconstants = require("../config/email.constants");
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
- // ==================== Factory Methods ====================
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
- // ==================== Feature Flags ====================
98
- /**
99
- * Get global enable company feature flag
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
- // ==================== Overrides ====================
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 = await this.getEmailEntities(enableCompanyFeature);
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 Error('No database config available. Provide defaultDatabaseConfig or tenantDefaultDatabaseConfig.');
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(emailOptions, request){
193
- super(EmailDataSourceProvider.buildParentOptions(emailOptions), request), _define_property(this, "emailOptions", void 0), _define_property(this, "logger", void 0), this.emailOptions = emailOptions, this.logger = new _common.Logger(EmailDataSourceProvider.name);
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)(_emailconstants.EMAIL_MODULE_OPTIONS)),
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 _interfaces.EmailModuleOptions === "undefined" ? Object : _interfaces.EmailModuleOptions,
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 _config = require("../config");
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
- * Resolve entity class for this service
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
- let emailConfigEntity = {
53
+ const entity = {
58
54
  ...dto
59
55
  };
60
- // Set company fields if company feature is enabled
61
56
  if (this.emailConfig.isCompanyFeatureEnabled()) {
62
- emailConfigEntity.companyId = user?.companyId ?? null;
57
+ entity.companyId = user?.companyId ?? null;
63
58
  }
64
- return emailConfigEntity;
59
+ return entity;
65
60
  }
66
61
  async getSelectQuery(query, _user, select) {
67
- if (!select || !select.length) {
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
- const selectFields = select.map((field)=>`${this.entityName}.${field}`);
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
- const enableCompanyFeature = this.emailConfig.isCompanyFeatureEnabled();
96
- if (enableCompanyFeature && user?.companyId) {
97
- query.andWhere('emailConfig.companyId = :companyId', {
98
- companyId: user.companyId
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 await this.repository.findOne({
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
- if (defaultConfig) {
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
- if (this.emailConfig.isCompanyFeatureEnabled() && user?.companyId) {
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)(_config.EmailConfigService)),
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 _config.EmailConfigService === "undefined" ? Object : _config.EmailConfigService,
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 _config = require("../config");
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
- * 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) {
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
- from: dto.from || config.fromEmail || undefined,
104
- fromName: dto.fromName || config.fromName || this.emailConfigService.getDefaultFromName(),
57
+ ...sender,
105
58
  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
- }))
59
+ attachments: this.buildAttachments(dto.attachments)
111
60
  });
112
- this.logger.log(`Email sent to ${Array.isArray(dto.to) ? dto.to.join(', ') : dto.to}: ${result.success ? 'SUCCESS' : 'FAILED'}`);
61
+ this.logResult(dto.to, result);
113
62
  return result;
114
63
  }
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
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
- from: dto.from || config.fromEmail || undefined,
164
- fromName: dto.fromName || config.fromName || this.emailConfigService.getDefaultFromName(),
76
+ ...sender,
165
77
  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
- }))
78
+ attachments: this.buildAttachments(dto.attachments)
171
79
  });
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'}`);
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.logger.log(`Test email sent to ${recipient}: ${result.success ? 'SUCCESS' : 'FAILED'}`);
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)(_config.EmailConfigService)),
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 _config.EmailConfigService === "undefined" ? Object : _config.EmailConfigService,
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
  ])