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