@flusys/nestjs-email 1.1.0-beta → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +589 -0
- package/cjs/config/email.constants.js +0 -18
- package/cjs/config/index.js +0 -1
- package/cjs/controllers/email-config.controller.js +46 -4
- package/cjs/controllers/email-send.controller.js +13 -26
- package/cjs/controllers/email-template.controller.js +60 -11
- package/cjs/docs/email-swagger.config.js +18 -80
- package/cjs/dtos/email-config.dto.js +6 -106
- package/cjs/dtos/email-send.dto.js +101 -123
- package/cjs/dtos/email-template.dto.js +41 -103
- package/cjs/entities/email-config-with-company.entity.js +2 -2
- package/cjs/entities/email-config.entity.js +92 -3
- package/cjs/entities/email-template-with-company.entity.js +5 -3
- package/cjs/entities/email-template.entity.js +119 -3
- package/cjs/entities/index.js +34 -19
- package/cjs/index.js +1 -0
- package/cjs/interfaces/email-provider.interface.js +1 -3
- package/cjs/modules/email.module.js +50 -104
- package/cjs/providers/email-factory.service.js +37 -109
- package/cjs/providers/email-provider.registry.js +5 -15
- package/cjs/providers/mailgun-provider.js +54 -58
- package/cjs/providers/sendgrid-provider.js +68 -92
- package/cjs/providers/smtp-provider.js +58 -69
- package/cjs/{config → services}/email-config.service.js +9 -32
- package/cjs/services/email-datasource.provider.js +17 -104
- package/cjs/services/email-provider-config.service.js +28 -58
- package/cjs/services/email-send.service.js +120 -125
- package/cjs/services/email-template.service.js +62 -85
- package/cjs/services/index.js +2 -1
- package/cjs/utils/email-templates.util.js +64 -0
- package/cjs/utils/index.js +18 -0
- package/config/email.constants.d.ts +0 -9
- package/config/index.d.ts +0 -1
- package/controllers/email-send.controller.d.ts +5 -12
- package/controllers/email-template.controller.d.ts +5 -7
- package/dtos/email-config.dto.d.ts +5 -13
- package/dtos/email-send.dto.d.ts +17 -21
- package/dtos/email-template.dto.d.ts +5 -16
- package/entities/email-config-with-company.entity.d.ts +2 -2
- package/entities/email-config.entity.d.ts +10 -2
- package/entities/email-template-with-company.entity.d.ts +2 -2
- package/entities/email-template.entity.d.ts +13 -2
- package/entities/index.d.ts +9 -3
- package/fesm/config/email.constants.js +0 -9
- package/fesm/config/index.js +0 -1
- package/fesm/controllers/email-config.controller.js +49 -7
- package/fesm/controllers/email-send.controller.js +13 -26
- package/fesm/controllers/email-template.controller.js +61 -12
- package/fesm/docs/email-swagger.config.js +21 -86
- package/fesm/dtos/email-config.dto.js +9 -115
- package/fesm/dtos/email-send.dto.js +103 -139
- package/fesm/dtos/email-template.dto.js +43 -111
- package/fesm/entities/email-config-with-company.entity.js +2 -2
- package/fesm/entities/email-config.entity.js +93 -4
- package/fesm/entities/email-template-with-company.entity.js +5 -3
- package/fesm/entities/email-template.entity.js +120 -4
- package/fesm/entities/index.js +22 -16
- package/fesm/index.js +1 -0
- package/fesm/interfaces/email-config.interface.js +1 -3
- package/fesm/interfaces/email-module-options.interface.js +1 -3
- package/fesm/interfaces/email-provider.interface.js +1 -5
- package/fesm/interfaces/email-template.interface.js +1 -3
- package/fesm/modules/email.module.js +52 -106
- package/fesm/providers/email-factory.service.js +38 -69
- package/fesm/providers/email-provider.registry.js +6 -19
- package/fesm/providers/mailgun-provider.js +55 -63
- package/fesm/providers/sendgrid-provider.js +69 -97
- package/fesm/providers/smtp-provider.js +59 -73
- package/fesm/{config → services}/email-config.service.js +9 -32
- package/fesm/services/email-datasource.provider.js +18 -64
- package/fesm/services/email-provider-config.service.js +26 -56
- package/fesm/services/email-send.service.js +118 -123
- package/fesm/services/email-template.service.js +60 -83
- package/fesm/services/index.js +2 -1
- package/fesm/utils/email-templates.util.js +47 -0
- package/fesm/utils/index.js +1 -0
- package/index.d.ts +1 -0
- package/interfaces/email-config.interface.d.ts +6 -0
- package/interfaces/email-module-options.interface.d.ts +0 -5
- package/modules/email.module.d.ts +1 -2
- package/package.json +4 -4
- package/providers/email-factory.service.d.ts +4 -7
- package/providers/mailgun-provider.d.ts +6 -2
- package/providers/sendgrid-provider.d.ts +6 -2
- package/providers/smtp-provider.d.ts +7 -2
- package/services/email-config.service.d.ts +12 -0
- package/services/email-datasource.provider.d.ts +3 -6
- package/services/email-provider-config.service.d.ts +3 -3
- package/services/email-send.service.d.ts +11 -3
- package/services/email-template.service.d.ts +5 -4
- package/services/index.d.ts +2 -1
- package/utils/email-templates.util.d.ts +2 -0
- package/utils/index.d.ts +1 -0
- package/cjs/entities/email-config-base.entity.js +0 -111
- package/cjs/entities/email-template-base.entity.js +0 -134
- package/config/email-config.service.d.ts +0 -13
- package/entities/email-config-base.entity.d.ts +0 -11
- package/entities/email-template-base.entity.d.ts +0 -14
- package/fesm/entities/email-config-base.entity.js +0 -101
- package/fesm/entities/email-template-base.entity.js +0 -124
|
@@ -12,67 +12,38 @@ function _define_property(obj, key, value) {
|
|
|
12
12
|
return obj;
|
|
13
13
|
}
|
|
14
14
|
import { Logger } from '@nestjs/common';
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
* Initialize the SMTP provider with configuration
|
|
21
|
-
*/ async initialize(config) {
|
|
22
|
-
this.config = config;
|
|
23
|
-
this.logger.log(`Initializing SMTP: host=${config.host}, port=${config.port}, secure=${config.secure}, user=${config.auth?.user}`);
|
|
24
|
-
// Dynamic import nodemailer
|
|
15
|
+
const VERIFY_TIMEOUT_MS = 10_000;
|
|
16
|
+
const SEND_TIMEOUT_MS = 30_000;
|
|
17
|
+
export class SmtpProvider {
|
|
18
|
+
async initialize(config) {
|
|
19
|
+
this.logger.log(`Initializing SMTP: host=${config.host}, port=${config.port}`);
|
|
25
20
|
const nodemailer = await import('nodemailer');
|
|
26
|
-
// Determine secure mode: port 465 = implicit TLS, port 587 = STARTTLS
|
|
27
|
-
const isSecure = config.secure ?? config.port === 465;
|
|
28
21
|
this.transporter = nodemailer.createTransport({
|
|
29
22
|
host: config.host,
|
|
30
23
|
port: config.port,
|
|
31
|
-
secure:
|
|
24
|
+
secure: config.secure ?? config.port === 465,
|
|
32
25
|
auth: config.auth,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
socketTimeout: 30000,
|
|
37
|
-
// TLS options for better compatibility
|
|
26
|
+
connectionTimeout: VERIFY_TIMEOUT_MS,
|
|
27
|
+
greetingTimeout: VERIFY_TIMEOUT_MS,
|
|
28
|
+
socketTimeout: SEND_TIMEOUT_MS,
|
|
38
29
|
tls: {
|
|
39
|
-
rejectUnauthorized:
|
|
40
|
-
minVersion: 'TLSv1.2'
|
|
30
|
+
rejectUnauthorized: config.tls?.rejectUnauthorized ?? true,
|
|
31
|
+
minVersion: config.tls?.minVersion ?? 'TLSv1.2'
|
|
41
32
|
}
|
|
42
33
|
});
|
|
43
|
-
this.
|
|
44
|
-
// Verify connection with timeout
|
|
45
|
-
try {
|
|
46
|
-
const verifyPromise = this.transporter.verify();
|
|
47
|
-
const timeoutPromise = new Promise((_, reject)=>setTimeout(()=>reject(new Error('SMTP verification timeout')), 10000));
|
|
48
|
-
await Promise.race([
|
|
49
|
-
verifyPromise,
|
|
50
|
-
timeoutPromise
|
|
51
|
-
]);
|
|
52
|
-
this.logger.log(`SMTP Provider verified successfully: ${config.host}:${config.port}`);
|
|
53
|
-
} catch (error) {
|
|
54
|
-
this.logger.warn(`SMTP verification failed: ${error.message}`);
|
|
55
|
-
// Don't throw - allow initialization even if verification fails
|
|
56
|
-
// Connection will be retried on send
|
|
57
|
-
}
|
|
34
|
+
await this.verifyConnection(config);
|
|
58
35
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
success: false,
|
|
65
|
-
error: 'SMTP provider not initialized'
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
const toAddress = Array.isArray(options.to) ? options.to.join(', ') : options.to;
|
|
69
|
-
this.logger.log(`Sending email to: ${toAddress}, subject: ${options.subject}`);
|
|
36
|
+
async sendEmail(options) {
|
|
37
|
+
if (!this.transporter) return {
|
|
38
|
+
success: false,
|
|
39
|
+
error: 'SMTP provider not initialized'
|
|
40
|
+
};
|
|
70
41
|
try {
|
|
71
42
|
const mailOptions = {
|
|
72
|
-
from:
|
|
73
|
-
to:
|
|
74
|
-
cc: options.cc ?
|
|
75
|
-
bcc: options.bcc ?
|
|
43
|
+
from: this.formatFrom(options.from, options.fromName),
|
|
44
|
+
to: this.joinAddresses(options.to),
|
|
45
|
+
cc: options.cc ? this.joinAddresses(options.cc) : undefined,
|
|
46
|
+
bcc: options.bcc ? this.joinAddresses(options.bcc) : undefined,
|
|
76
47
|
subject: options.subject,
|
|
77
48
|
text: options.text,
|
|
78
49
|
html: options.html,
|
|
@@ -84,35 +55,20 @@ import { Logger } from '@nestjs/common';
|
|
|
84
55
|
encoding: a.encoding
|
|
85
56
|
}))
|
|
86
57
|
};
|
|
87
|
-
this.
|
|
88
|
-
// Send with timeout
|
|
89
|
-
const sendPromise = this.transporter.sendMail(mailOptions);
|
|
90
|
-
const timeoutPromise = new Promise((_, reject)=>setTimeout(()=>reject(new Error('SMTP send timeout after 30 seconds')), 30000));
|
|
91
|
-
const info = await Promise.race([
|
|
92
|
-
sendPromise,
|
|
93
|
-
timeoutPromise
|
|
94
|
-
]);
|
|
58
|
+
const info = await this.withTimeout(this.transporter.sendMail(mailOptions), SEND_TIMEOUT_MS, 'SMTP send timeout');
|
|
95
59
|
this.logger.log(`Email sent via SMTP: ${info.messageId}`);
|
|
96
60
|
return {
|
|
97
61
|
success: true,
|
|
98
62
|
messageId: info.messageId
|
|
99
63
|
};
|
|
100
64
|
} catch (error) {
|
|
101
|
-
this.
|
|
102
|
-
return {
|
|
103
|
-
success: false,
|
|
104
|
-
error: error.message
|
|
105
|
-
};
|
|
65
|
+
return this.handleError('SMTP send failed', error);
|
|
106
66
|
}
|
|
107
67
|
}
|
|
108
|
-
|
|
109
|
-
* Send multiple emails (batch)
|
|
110
|
-
*/ async sendBulkEmails(options) {
|
|
68
|
+
async sendBulkEmails(options) {
|
|
111
69
|
return Promise.all(options.map((opt)=>this.sendEmail(opt)));
|
|
112
70
|
}
|
|
113
|
-
|
|
114
|
-
* Health check for the provider
|
|
115
|
-
*/ async healthCheck() {
|
|
71
|
+
async healthCheck() {
|
|
116
72
|
if (!this.transporter) return false;
|
|
117
73
|
try {
|
|
118
74
|
await this.transporter.verify();
|
|
@@ -121,17 +77,47 @@ import { Logger } from '@nestjs/common';
|
|
|
121
77
|
return false;
|
|
122
78
|
}
|
|
123
79
|
}
|
|
124
|
-
|
|
125
|
-
* Close the transporter
|
|
126
|
-
*/ async close() {
|
|
80
|
+
async close() {
|
|
127
81
|
if (this.transporter) {
|
|
128
82
|
this.transporter.close();
|
|
129
83
|
this.transporter = null;
|
|
130
84
|
}
|
|
131
85
|
}
|
|
86
|
+
// ─── Private Helpers ────────────────────────────────────────────────────────
|
|
87
|
+
async verifyConnection(config) {
|
|
88
|
+
try {
|
|
89
|
+
await this.withTimeout(this.transporter.verify(), VERIFY_TIMEOUT_MS, 'SMTP verification timeout');
|
|
90
|
+
this.logger.log(`SMTP Provider verified: ${config.host}:${config.port}`);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
this.logger.warn(`SMTP verification failed: ${this.extractError(error)}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
formatFrom(from, fromName) {
|
|
96
|
+
return fromName ? `"${fromName}" <${from}>` : from;
|
|
97
|
+
}
|
|
98
|
+
joinAddresses(addresses) {
|
|
99
|
+
return Array.isArray(addresses) ? addresses.join(', ') : addresses;
|
|
100
|
+
}
|
|
101
|
+
async withTimeout(promise, ms, message) {
|
|
102
|
+
const timeout = new Promise((_, reject)=>setTimeout(()=>reject(new Error(message)), ms));
|
|
103
|
+
return Promise.race([
|
|
104
|
+
promise,
|
|
105
|
+
timeout
|
|
106
|
+
]);
|
|
107
|
+
}
|
|
108
|
+
extractError(error) {
|
|
109
|
+
return error instanceof Error ? error.message : 'Unknown error';
|
|
110
|
+
}
|
|
111
|
+
handleError(context, error) {
|
|
112
|
+
const message = this.extractError(error);
|
|
113
|
+
this.logger.error(`${context}: ${message}`, error instanceof Error ? error.stack : undefined);
|
|
114
|
+
return {
|
|
115
|
+
success: false,
|
|
116
|
+
error: message
|
|
117
|
+
};
|
|
118
|
+
}
|
|
132
119
|
constructor(){
|
|
133
120
|
_define_property(this, "logger", new Logger(SmtpProvider.name));
|
|
134
121
|
_define_property(this, "transporter", null);
|
|
135
|
-
_define_property(this, "config", null);
|
|
136
122
|
}
|
|
137
123
|
}
|
|
@@ -26,47 +26,24 @@ function _ts_param(paramIndex, decorator) {
|
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
28
|
import { Inject, Injectable } from '@nestjs/common';
|
|
29
|
+
import { EMAIL_MODULE_OPTIONS, DEFAULT_FROM_NAME } from '../config/email.constants';
|
|
29
30
|
import { EmailModuleOptions } from '../interfaces';
|
|
30
|
-
import { EMAIL_MODULE_OPTIONS, DEFAULT_FROM_NAME } from './email.constants';
|
|
31
31
|
export class EmailConfigService {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
*/ isCompanyFeatureEnabled() {
|
|
32
|
+
// ─── IModuleConfigService Implementation ────────────────────────────────────
|
|
33
|
+
isCompanyFeatureEnabled() {
|
|
35
34
|
return this.options.bootstrapAppConfig?.enableCompanyFeature ?? false;
|
|
36
35
|
}
|
|
37
|
-
|
|
38
|
-
* Get database mode
|
|
39
|
-
*/ getDatabaseMode() {
|
|
36
|
+
getDatabaseMode() {
|
|
40
37
|
return this.options.bootstrapAppConfig?.databaseMode ?? 'single';
|
|
41
38
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
*/ getRateLimitPerMinute() {
|
|
45
|
-
return this.options.config?.rateLimitPerMinute ?? 60;
|
|
39
|
+
isMultiTenant() {
|
|
40
|
+
return this.getDatabaseMode() === 'multi-tenant';
|
|
46
41
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
*/ isLoggingEnabled() {
|
|
50
|
-
return this.options.config?.enableLogging ?? false;
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Get default provider type
|
|
54
|
-
*/ getDefaultProvider() {
|
|
55
|
-
return this.options.config?.defaultProvider;
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Get default from name
|
|
59
|
-
*/ getDefaultFromName() {
|
|
42
|
+
// ─── Config Getters ─────────────────────────────────────────────────────────
|
|
43
|
+
getDefaultFromName() {
|
|
60
44
|
return DEFAULT_FROM_NAME;
|
|
61
45
|
}
|
|
62
|
-
|
|
63
|
-
* Get default database config
|
|
64
|
-
*/ getDefaultDatabaseConfig() {
|
|
65
|
-
return this.options.config?.defaultDatabaseConfig;
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Get all options
|
|
69
|
-
*/ getOptions() {
|
|
46
|
+
getOptions() {
|
|
70
47
|
return this.options;
|
|
71
48
|
}
|
|
72
49
|
constructor(options){
|
|
@@ -26,16 +26,14 @@ function _ts_param(paramIndex, decorator) {
|
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
28
|
import { MultiTenantDataSourceService } from '@flusys/nestjs-shared/modules';
|
|
29
|
-
import { Inject, Injectable, Logger, Optional, Scope } from '@nestjs/common';
|
|
29
|
+
import { Inject, Injectable, InternalServerErrorException, Logger, Optional, Scope } from '@nestjs/common';
|
|
30
30
|
import { REQUEST } from '@nestjs/core';
|
|
31
31
|
import { Request } from 'express';
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
32
|
+
import { getEmailEntitiesByConfig } from '../entities';
|
|
33
|
+
import { EmailConfigService } from './email-config.service';
|
|
34
34
|
export class EmailDataSourceProvider extends MultiTenantDataSourceService {
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
* Build parent options from EmailModuleOptions
|
|
38
|
-
*/ static buildParentOptions(options) {
|
|
35
|
+
// ─── Factory Methods ────────────────────────────────────────────────────────
|
|
36
|
+
static buildParentOptions(options) {
|
|
39
37
|
return {
|
|
40
38
|
bootstrapAppConfig: options.bootstrapAppConfig,
|
|
41
39
|
defaultDatabaseConfig: options.config?.defaultDatabaseConfig,
|
|
@@ -43,66 +41,28 @@ export class EmailDataSourceProvider extends MultiTenantDataSourceService {
|
|
|
43
41
|
tenants: options.config?.tenants
|
|
44
42
|
};
|
|
45
43
|
}
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
*/ getEnableCompanyFeature() {
|
|
50
|
-
return this.emailOptions.bootstrapAppConfig?.enableCompanyFeature ?? false;
|
|
44
|
+
// ─── Feature Flags ──────────────────────────────────────────────────────────
|
|
45
|
+
getEnableCompanyFeatureForTenant(tenant) {
|
|
46
|
+
return tenant?.enableCompanyFeature ?? this.configService.isCompanyFeatureEnabled();
|
|
51
47
|
}
|
|
52
|
-
|
|
53
|
-
|
|
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) {
|
|
48
|
+
// ─── Overrides ──────────────────────────────────────────────────────────────
|
|
49
|
+
async createDataSourceFromConfig(config) {
|
|
85
50
|
const currentTenant = this.getCurrentTenant();
|
|
86
51
|
const enableCompanyFeature = this.getEnableCompanyFeatureForTenant(currentTenant ?? undefined);
|
|
87
|
-
const entities =
|
|
52
|
+
const entities = getEmailEntitiesByConfig(enableCompanyFeature);
|
|
88
53
|
return super.createDataSourceFromConfig(config, entities);
|
|
89
54
|
}
|
|
90
|
-
|
|
91
|
-
* Override to use Email-specific static cache
|
|
92
|
-
*/ async getSingleDataSource() {
|
|
93
|
-
// Return existing initialized connection from Email-specific cache
|
|
55
|
+
async getSingleDataSource() {
|
|
94
56
|
if (EmailDataSourceProvider.singleDataSource?.isInitialized) {
|
|
95
57
|
return EmailDataSourceProvider.singleDataSource;
|
|
96
58
|
}
|
|
97
|
-
// If another request is creating the connection, wait for it
|
|
98
59
|
if (EmailDataSourceProvider.singleConnectionLock) {
|
|
99
60
|
return EmailDataSourceProvider.singleConnectionLock;
|
|
100
61
|
}
|
|
101
62
|
const config = this.getDefaultDatabaseConfig();
|
|
102
63
|
if (!config) {
|
|
103
|
-
throw new
|
|
64
|
+
throw new InternalServerErrorException('No database config available. Provide defaultDatabaseConfig or tenantDefaultDatabaseConfig.');
|
|
104
65
|
}
|
|
105
|
-
// Create connection with lock to prevent race conditions
|
|
106
66
|
const connectionPromise = this.createDataSourceFromConfig(config);
|
|
107
67
|
EmailDataSourceProvider.singleConnectionLock = connectionPromise;
|
|
108
68
|
try {
|
|
@@ -113,20 +73,15 @@ export class EmailDataSourceProvider extends MultiTenantDataSourceService {
|
|
|
113
73
|
EmailDataSourceProvider.singleConnectionLock = null;
|
|
114
74
|
}
|
|
115
75
|
}
|
|
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
|
|
76
|
+
async getOrCreateTenantConnection(tenant) {
|
|
120
77
|
const existing = EmailDataSourceProvider.tenantConnections.get(tenant.id);
|
|
121
78
|
if (existing?.isInitialized) {
|
|
122
79
|
return existing;
|
|
123
80
|
}
|
|
124
|
-
// If another request is creating this tenant's connection, wait for it
|
|
125
81
|
const pendingConnection = EmailDataSourceProvider.connectionLocks.get(tenant.id);
|
|
126
82
|
if (pendingConnection) {
|
|
127
83
|
return pendingConnection;
|
|
128
84
|
}
|
|
129
|
-
// Create connection with lock to prevent race conditions
|
|
130
85
|
const config = this.buildTenantDatabaseConfig(tenant);
|
|
131
86
|
const connectionPromise = this.createDataSourceFromConfig(config);
|
|
132
87
|
EmailDataSourceProvider.connectionLocks.set(tenant.id, connectionPromise);
|
|
@@ -138,11 +93,10 @@ export class EmailDataSourceProvider extends MultiTenantDataSourceService {
|
|
|
138
93
|
EmailDataSourceProvider.connectionLocks.delete(tenant.id);
|
|
139
94
|
}
|
|
140
95
|
}
|
|
141
|
-
constructor(
|
|
142
|
-
super(EmailDataSourceProvider.buildParentOptions(
|
|
96
|
+
constructor(configService, request){
|
|
97
|
+
super(EmailDataSourceProvider.buildParentOptions(configService.getOptions()), request), _define_property(this, "configService", void 0), _define_property(this, "logger", void 0), this.configService = configService, this.logger = new Logger(EmailDataSourceProvider.name);
|
|
143
98
|
}
|
|
144
99
|
}
|
|
145
|
-
// Override parent's static properties to have Email-specific cache
|
|
146
100
|
_define_property(EmailDataSourceProvider, "tenantConnections", new Map());
|
|
147
101
|
_define_property(EmailDataSourceProvider, "singleDataSource", null);
|
|
148
102
|
_define_property(EmailDataSourceProvider, "tenantsRegistry", new Map());
|
|
@@ -153,12 +107,12 @@ EmailDataSourceProvider = _ts_decorate([
|
|
|
153
107
|
Injectable({
|
|
154
108
|
scope: Scope.REQUEST
|
|
155
109
|
}),
|
|
156
|
-
_ts_param(0, Inject(
|
|
110
|
+
_ts_param(0, Inject(EmailConfigService)),
|
|
157
111
|
_ts_param(1, Optional()),
|
|
158
112
|
_ts_param(1, Inject(REQUEST)),
|
|
159
113
|
_ts_metadata("design:type", Function),
|
|
160
114
|
_ts_metadata("design:paramtypes", [
|
|
161
|
-
typeof
|
|
115
|
+
typeof EmailConfigService === "undefined" ? Object : EmailConfigService,
|
|
162
116
|
typeof Request === "undefined" ? Object : Request
|
|
163
117
|
])
|
|
164
118
|
], EmailDataSourceProvider);
|
|
@@ -27,34 +27,29 @@ function _ts_param(paramIndex, decorator) {
|
|
|
27
27
|
}
|
|
28
28
|
import { RequestScopedApiService, HybridCache } from '@flusys/nestjs-shared/classes';
|
|
29
29
|
import { UtilsService } from '@flusys/nestjs-shared/modules';
|
|
30
|
+
import { applyCompanyFilter, buildCompanyWhereCondition } from '@flusys/nestjs-shared/utils';
|
|
30
31
|
import { Inject, Injectable, Scope } from '@nestjs/common';
|
|
31
|
-
import { EmailConfigService } from '
|
|
32
|
+
import { EmailConfigService } from './email-config.service';
|
|
32
33
|
import { EmailConfig, EmailConfigWithCompany } from '../entities';
|
|
33
34
|
import { EmailDataSourceProvider } from './email-datasource.provider';
|
|
34
35
|
export class EmailProviderConfigService extends RequestScopedApiService {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
*/ resolveEntity() {
|
|
38
|
-
const enableCompanyFeature = this.emailConfig.isCompanyFeatureEnabled();
|
|
39
|
-
return enableCompanyFeature ? EmailConfigWithCompany : EmailConfig;
|
|
36
|
+
resolveEntity() {
|
|
37
|
+
return this.emailConfig.isCompanyFeatureEnabled() ? EmailConfigWithCompany : EmailConfig;
|
|
40
38
|
}
|
|
41
|
-
|
|
42
|
-
* Get DataSource provider for this service
|
|
43
|
-
*/ getDataSourceProvider() {
|
|
39
|
+
getDataSourceProvider() {
|
|
44
40
|
return this.dataSourceProvider;
|
|
45
41
|
}
|
|
46
42
|
async convertSingleDtoToEntity(dto, user) {
|
|
47
|
-
|
|
43
|
+
const entity = {
|
|
48
44
|
...dto
|
|
49
45
|
};
|
|
50
|
-
// Set company fields if company feature is enabled
|
|
51
46
|
if (this.emailConfig.isCompanyFeatureEnabled()) {
|
|
52
|
-
|
|
47
|
+
entity.companyId = user?.companyId ?? null;
|
|
53
48
|
}
|
|
54
|
-
return
|
|
49
|
+
return entity;
|
|
55
50
|
}
|
|
56
51
|
async getSelectQuery(query, _user, select) {
|
|
57
|
-
if (!select
|
|
52
|
+
if (!select?.length) {
|
|
58
53
|
select = [
|
|
59
54
|
'id',
|
|
60
55
|
'name',
|
|
@@ -67,52 +62,36 @@ export class EmailProviderConfigService extends RequestScopedApiService {
|
|
|
67
62
|
'createdAt',
|
|
68
63
|
'updatedAt'
|
|
69
64
|
];
|
|
70
|
-
if (this.emailConfig.isCompanyFeatureEnabled())
|
|
71
|
-
select.push('companyId');
|
|
72
|
-
}
|
|
65
|
+
if (this.emailConfig.isCompanyFeatureEnabled()) select.push('companyId');
|
|
73
66
|
}
|
|
74
|
-
|
|
75
|
-
query.select(selectFields);
|
|
67
|
+
query.select(select.map((field)=>`${this.entityName}.${field}`));
|
|
76
68
|
return {
|
|
77
69
|
query,
|
|
78
70
|
isRaw: false
|
|
79
71
|
};
|
|
80
72
|
}
|
|
81
|
-
|
|
82
|
-
* Override: Extra query manipulation - Auto-filter by user's company
|
|
83
|
-
*/ async getExtraManipulateQuery(query, filterDto, user) {
|
|
73
|
+
async getExtraManipulateQuery(query, filterDto, user) {
|
|
84
74
|
const result = await super.getExtraManipulateQuery(query, filterDto, user);
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
});
|
|
90
|
-
}
|
|
75
|
+
applyCompanyFilter(query, {
|
|
76
|
+
isCompanyFeatureEnabled: this.emailConfig.isCompanyFeatureEnabled(),
|
|
77
|
+
entityAlias: 'emailConfig'
|
|
78
|
+
}, user);
|
|
91
79
|
query.orderBy(`${this.entityName}.createdAt`, 'DESC');
|
|
92
80
|
return result;
|
|
93
81
|
}
|
|
94
|
-
|
|
95
|
-
* Find config by ID directly (bypasses company filtering)
|
|
96
|
-
*/ async findByIdDirect(id) {
|
|
82
|
+
async findByIdDirect(id) {
|
|
97
83
|
await this.ensureRepositoryInitialized();
|
|
98
|
-
return
|
|
84
|
+
return this.repository.findOne({
|
|
99
85
|
where: {
|
|
100
86
|
id
|
|
101
87
|
}
|
|
102
88
|
});
|
|
103
89
|
}
|
|
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) {
|
|
90
|
+
async getDefaultConfig(user) {
|
|
108
91
|
await this.ensureRepositoryInitialized();
|
|
109
|
-
const baseWhere = {
|
|
92
|
+
const baseWhere = buildCompanyWhereCondition({
|
|
110
93
|
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
|
|
94
|
+
}, this.emailConfig.isCompanyFeatureEnabled(), user);
|
|
116
95
|
const defaultConfig = await this.repository.findOne({
|
|
117
96
|
where: {
|
|
118
97
|
...baseWhere,
|
|
@@ -122,29 +101,20 @@ export class EmailProviderConfigService extends RequestScopedApiService {
|
|
|
122
101
|
createdAt: 'ASC'
|
|
123
102
|
}
|
|
124
103
|
});
|
|
125
|
-
|
|
126
|
-
return defaultConfig;
|
|
127
|
-
}
|
|
128
|
-
// Fall back to any available active config (oldest first for consistency)
|
|
129
|
-
return await this.repository.findOne({
|
|
104
|
+
return defaultConfig || this.repository.findOne({
|
|
130
105
|
where: baseWhere,
|
|
131
106
|
order: {
|
|
132
107
|
createdAt: 'ASC'
|
|
133
108
|
}
|
|
134
109
|
});
|
|
135
110
|
}
|
|
136
|
-
|
|
137
|
-
* Get config by provider type
|
|
138
|
-
*/ async getConfigByProvider(provider, user) {
|
|
111
|
+
async getConfigByProvider(provider, user) {
|
|
139
112
|
await this.ensureRepositoryInitialized();
|
|
140
|
-
const where = {
|
|
113
|
+
const where = buildCompanyWhereCondition({
|
|
141
114
|
provider,
|
|
142
115
|
isActive: true
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
where.companyId = user.companyId;
|
|
146
|
-
}
|
|
147
|
-
return await this.repository.find({
|
|
116
|
+
}, this.emailConfig.isCompanyFeatureEnabled(), user);
|
|
117
|
+
return this.repository.find({
|
|
148
118
|
where
|
|
149
119
|
});
|
|
150
120
|
}
|