@flusys/nestjs-email 1.1.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cjs/config/email-config.service.js +94 -0
- package/cjs/config/email.constants.js +40 -0
- package/cjs/config/index.js +19 -0
- package/cjs/controllers/email-config.controller.js +59 -0
- package/cjs/controllers/email-send.controller.js +142 -0
- package/cjs/controllers/email-template.controller.js +84 -0
- package/cjs/controllers/index.js +20 -0
- package/cjs/docs/email-swagger.config.js +176 -0
- package/cjs/docs/index.js +11 -0
- package/cjs/dtos/email-config.dto.js +238 -0
- package/cjs/dtos/email-send.dto.js +376 -0
- package/cjs/dtos/email-template.dto.js +283 -0
- package/cjs/dtos/index.js +20 -0
- package/cjs/entities/email-config-base.entity.js +111 -0
- package/cjs/entities/email-config-with-company.entity.js +63 -0
- package/cjs/entities/email-config.entity.js +25 -0
- package/cjs/entities/email-template-base.entity.js +134 -0
- package/cjs/entities/email-template-with-company.entity.js +63 -0
- package/cjs/entities/email-template.entity.js +25 -0
- package/cjs/entities/index.js +41 -0
- package/cjs/enums/email-provider-type.enum.js +18 -0
- package/cjs/enums/index.js +18 -0
- package/cjs/index.js +27 -0
- package/cjs/interfaces/email-config.interface.js +4 -0
- package/cjs/interfaces/email-module-options.interface.js +4 -0
- package/cjs/interfaces/email-provider.interface.js +6 -0
- package/cjs/interfaces/email-template.interface.js +4 -0
- package/cjs/interfaces/index.js +21 -0
- package/cjs/modules/email.module.js +177 -0
- package/cjs/modules/index.js +18 -0
- package/cjs/providers/email-factory.service.js +160 -0
- package/cjs/providers/email-provider.registry.js +51 -0
- package/cjs/providers/index.js +22 -0
- package/cjs/providers/mailgun-provider.js +125 -0
- package/cjs/providers/sendgrid-provider.js +156 -0
- package/cjs/providers/smtp-provider.js +185 -0
- package/cjs/services/email-datasource.provider.js +215 -0
- package/cjs/services/email-provider-config.service.js +180 -0
- package/cjs/services/email-send.service.js +228 -0
- package/cjs/services/email-template.service.js +186 -0
- package/cjs/services/index.js +21 -0
- package/config/email-config.service.d.ts +13 -0
- package/config/email.constants.d.ts +11 -0
- package/config/index.d.ts +2 -0
- package/controllers/email-config.controller.d.ts +17 -0
- package/controllers/email-send.controller.d.ts +19 -0
- package/controllers/email-template.controller.d.ts +25 -0
- package/controllers/index.d.ts +3 -0
- package/docs/email-swagger.config.d.ts +3 -0
- package/docs/index.d.ts +1 -0
- package/dtos/email-config.dto.d.ts +33 -0
- package/dtos/email-send.dto.d.ts +45 -0
- package/dtos/email-template.dto.d.ts +42 -0
- package/dtos/index.d.ts +3 -0
- package/entities/email-config-base.entity.d.ts +11 -0
- package/entities/email-config-with-company.entity.d.ts +4 -0
- package/entities/email-config.entity.d.ts +3 -0
- package/entities/email-template-base.entity.d.ts +14 -0
- package/entities/email-template-with-company.entity.d.ts +4 -0
- package/entities/email-template.entity.d.ts +3 -0
- package/entities/index.d.ts +7 -0
- package/enums/email-provider-type.enum.d.ts +5 -0
- package/enums/index.d.ts +1 -0
- package/fesm/config/email-config.service.js +84 -0
- package/fesm/config/email.constants.js +13 -0
- package/fesm/config/index.js +2 -0
- package/fesm/controllers/email-config.controller.js +49 -0
- package/fesm/controllers/email-send.controller.js +132 -0
- package/fesm/controllers/email-template.controller.js +74 -0
- package/fesm/controllers/index.js +3 -0
- package/fesm/docs/email-swagger.config.js +172 -0
- package/fesm/docs/index.js +1 -0
- package/fesm/dtos/email-config.dto.js +223 -0
- package/fesm/dtos/email-send.dto.js +360 -0
- package/fesm/dtos/email-template.dto.js +268 -0
- package/fesm/dtos/index.js +3 -0
- package/fesm/entities/email-config-base.entity.js +101 -0
- package/fesm/entities/email-config-with-company.entity.js +53 -0
- package/fesm/entities/email-config.entity.js +15 -0
- package/fesm/entities/email-template-base.entity.js +124 -0
- package/fesm/entities/email-template-with-company.entity.js +53 -0
- package/fesm/entities/email-template.entity.js +15 -0
- package/fesm/entities/index.js +20 -0
- package/fesm/enums/email-provider-type.enum.js +8 -0
- package/fesm/enums/index.js +1 -0
- package/fesm/index.js +10 -0
- package/fesm/interfaces/email-config.interface.js +3 -0
- package/fesm/interfaces/email-module-options.interface.js +3 -0
- package/fesm/interfaces/email-provider.interface.js +5 -0
- package/fesm/interfaces/email-template.interface.js +3 -0
- package/fesm/interfaces/index.js +4 -0
- package/fesm/modules/email.module.js +167 -0
- package/fesm/modules/index.js +1 -0
- package/fesm/providers/email-factory.service.js +109 -0
- package/fesm/providers/email-provider.registry.js +44 -0
- package/fesm/providers/index.js +5 -0
- package/fesm/providers/mailgun-provider.js +119 -0
- package/fesm/providers/sendgrid-provider.js +150 -0
- package/fesm/providers/smtp-provider.js +137 -0
- package/fesm/services/email-datasource.provider.js +164 -0
- package/fesm/services/email-provider-config.service.js +170 -0
- package/fesm/services/email-send.service.js +218 -0
- package/fesm/services/email-template.service.js +176 -0
- package/fesm/services/index.js +4 -0
- package/index.d.ts +9 -0
- package/interfaces/email-config.interface.d.ts +28 -0
- package/interfaces/email-module-options.interface.d.ts +26 -0
- package/interfaces/email-provider.interface.d.ts +34 -0
- package/interfaces/email-template.interface.d.ts +64 -0
- package/interfaces/index.d.ts +4 -0
- package/modules/email.module.d.ts +9 -0
- package/modules/index.d.ts +1 -0
- package/package.json +105 -0
- package/providers/email-factory.service.d.ts +14 -0
- package/providers/email-provider.registry.d.ts +10 -0
- package/providers/index.d.ts +5 -0
- package/providers/mailgun-provider.d.ts +11 -0
- package/providers/sendgrid-provider.d.ts +11 -0
- package/providers/smtp-provider.d.ts +11 -0
- package/services/email-datasource.provider.d.ts +25 -0
- package/services/email-provider-config.service.d.ts +32 -0
- package/services/email-send.service.d.ts +20 -0
- package/services/email-template.service.d.ts +31 -0
- package/services/index.d.ts +4 -0
|
@@ -0,0 +1,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);
|