@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.
Files changed (100) hide show
  1. package/README.md +589 -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 +92 -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 +10 -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 +93 -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 +4 -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,67 +12,38 @@ function _define_property(obj, key, value) {
12
12
  return obj;
13
13
  }
14
14
  import { Logger } from '@nestjs/common';
15
- /**
16
- * SMTP Email Provider using nodemailer
17
- * Default provider - always available
18
- */ export class SmtpProvider {
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: isSecure,
24
+ secure: config.secure ?? config.port === 465,
32
25
  auth: config.auth,
33
- // Timeout settings to prevent hanging
34
- connectionTimeout: 10000,
35
- greetingTimeout: 10000,
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: false,
40
- minVersion: 'TLSv1.2'
30
+ rejectUnauthorized: config.tls?.rejectUnauthorized ?? true,
31
+ minVersion: config.tls?.minVersion ?? 'TLSv1.2'
41
32
  }
42
33
  });
43
- this.logger.log(`SMTP transporter created with secure=${isSecure}`);
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
- * Send a single email
61
- */ async sendEmail(options) {
62
- if (!this.transporter) {
63
- return {
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: options.fromName ? `"${options.fromName}" <${options.from}>` : options.from,
73
- to: toAddress,
74
- cc: options.cc ? Array.isArray(options.cc) ? options.cc.join(', ') : options.cc : undefined,
75
- bcc: options.bcc ? Array.isArray(options.bcc) ? options.bcc.join(', ') : options.bcc : undefined,
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.logger.log(`Mail options: from=${mailOptions.from}, to=${mailOptions.to}`);
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.logger.error(`SMTP send failed: ${error.message}`, error.stack);
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
- * Check if company feature is enabled
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
- * Get rate limit per minute
44
- */ getRateLimitPerMinute() {
45
- return this.options.config?.rateLimitPerMinute ?? 60;
39
+ isMultiTenant() {
40
+ return this.getDatabaseMode() === 'multi-tenant';
46
41
  }
47
- /**
48
- * Check if email logging is enabled
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 { EmailModuleOptions } from '../interfaces';
33
- import { EMAIL_MODULE_OPTIONS } from '../config/email.constants';
32
+ import { getEmailEntitiesByConfig } from '../entities';
33
+ import { EmailConfigService } from './email-config.service';
34
34
  export class EmailDataSourceProvider extends MultiTenantDataSourceService {
35
- // ==================== Factory Methods ====================
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
- // ==================== Feature Flags ====================
47
- /**
48
- * Get global enable company feature flag
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
- * 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) {
48
+ // ─── Overrides ──────────────────────────────────────────────────────────────
49
+ async createDataSourceFromConfig(config) {
85
50
  const currentTenant = this.getCurrentTenant();
86
51
  const enableCompanyFeature = this.getEnableCompanyFeatureForTenant(currentTenant ?? undefined);
87
- const entities = await this.getEmailEntities(enableCompanyFeature);
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 Error('No database config available. Provide defaultDatabaseConfig or tenantDefaultDatabaseConfig.');
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(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);
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(EMAIL_MODULE_OPTIONS)),
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 EmailModuleOptions === "undefined" ? Object : EmailModuleOptions,
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 '../config';
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
- * Resolve entity class for this service
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
- let emailConfigEntity = {
43
+ const entity = {
48
44
  ...dto
49
45
  };
50
- // Set company fields if company feature is enabled
51
46
  if (this.emailConfig.isCompanyFeatureEnabled()) {
52
- emailConfigEntity.companyId = user?.companyId ?? null;
47
+ entity.companyId = user?.companyId ?? null;
53
48
  }
54
- return emailConfigEntity;
49
+ return entity;
55
50
  }
56
51
  async getSelectQuery(query, _user, select) {
57
- if (!select || !select.length) {
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
- const selectFields = select.map((field)=>`${this.entityName}.${field}`);
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
- const enableCompanyFeature = this.emailConfig.isCompanyFeatureEnabled();
86
- if (enableCompanyFeature && user?.companyId) {
87
- query.andWhere('emailConfig.companyId = :companyId', {
88
- companyId: user.companyId
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 await this.repository.findOne({
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
- if (defaultConfig) {
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
- if (this.emailConfig.isCompanyFeatureEnabled() && user?.companyId) {
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
  }