@flusys/nestjs-email 7.1.0 → 8.0.0-beta.1

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 (159) hide show
  1. package/config/index.d.ts +3 -3
  2. package/controllers/email-config.controller.d.ts +2 -2
  3. package/controllers/email-send.controller.d.ts +3 -3
  4. package/controllers/email-template.controller.d.ts +4 -4
  5. package/controllers/index.d.ts +3 -3
  6. package/docs/email-swagger.config.d.ts +2 -2
  7. package/docs/index.d.ts +1 -1
  8. package/dtos/email-config.dto.d.ts +1 -1
  9. package/dtos/email-send.dto.d.ts +1 -1
  10. package/dtos/index.d.ts +3 -3
  11. package/entities/email-config-with-company.entity.d.ts +1 -1
  12. package/entities/email-template-with-company.entity.d.ts +1 -1
  13. package/entities/index.d.ts +10 -10
  14. package/enums/index.d.ts +1 -1
  15. package/fesm/261.js +799 -0
  16. package/fesm/493.js +1723 -0
  17. package/fesm/chunks/99.js +12 -0
  18. package/fesm/config/index.js +104 -3
  19. package/fesm/controllers/index.js +396 -3
  20. package/fesm/docs/index.js +134 -1
  21. package/fesm/dtos/index.js +22 -3
  22. package/fesm/entities/index.js +409 -13
  23. package/fesm/enums/index.js +37 -1
  24. package/fesm/index.js +762 -10
  25. package/fesm/interfaces/index.js +18 -4
  26. package/fesm/modules/index.js +541 -1
  27. package/fesm/providers/index.js +523 -5
  28. package/fesm/runtime.js +134 -0
  29. package/fesm/services/index.js +17 -5
  30. package/index.d.ts +10 -10
  31. package/interfaces/email-config.interface.d.ts +2 -2
  32. package/interfaces/email-module-options.interface.d.ts +3 -3
  33. package/interfaces/email-template.interface.d.ts +1 -1
  34. package/interfaces/index.d.ts +4 -4
  35. package/modules/email.module.d.ts +2 -2
  36. package/modules/index.d.ts +1 -1
  37. package/package.json +27 -38
  38. package/providers/email-factory.service.d.ts +2 -2
  39. package/providers/email-provider.registry.d.ts +2 -2
  40. package/providers/index.d.ts +5 -5
  41. package/providers/mailgun-provider.d.ts +1 -1
  42. package/providers/sendgrid-provider.d.ts +1 -1
  43. package/providers/smtp-provider.d.ts +1 -1
  44. package/services/email-config.service.d.ts +3 -3
  45. package/services/email-datasource.provider.d.ts +3 -3
  46. package/services/email-provider-config.service.d.ts +7 -7
  47. package/services/email-send.service.d.ts +7 -7
  48. package/services/email-template.service.d.ts +6 -6
  49. package/services/index.d.ts +5 -5
  50. package/utils/index.d.ts +1 -1
  51. package/cjs/config/email.constants.js +0 -22
  52. package/cjs/config/event-actions.js +0 -34
  53. package/cjs/config/index.js +0 -20
  54. package/cjs/config/message-keys.js +0 -39
  55. package/cjs/controllers/email-config.controller.js +0 -111
  56. package/cjs/controllers/email-config.controller.spec.js +0 -85
  57. package/cjs/controllers/email-send.controller.js +0 -128
  58. package/cjs/controllers/email-send.controller.spec.js +0 -139
  59. package/cjs/controllers/email-template.controller.js +0 -142
  60. package/cjs/controllers/email-template.controller.spec.js +0 -89
  61. package/cjs/controllers/index.js +0 -20
  62. package/cjs/docs/email-swagger.config.js +0 -116
  63. package/cjs/docs/email-swagger.config.spec.js +0 -62
  64. package/cjs/docs/index.js +0 -11
  65. package/cjs/dtos/email-config.dto.js +0 -212
  66. package/cjs/dtos/email-send.dto.js +0 -354
  67. package/cjs/dtos/email-template.dto.js +0 -252
  68. package/cjs/dtos/index.js +0 -20
  69. package/cjs/entities/email-config-with-company.entity.js +0 -63
  70. package/cjs/entities/email-config.entity.js +0 -114
  71. package/cjs/entities/email-template-with-company.entity.js +0 -65
  72. package/cjs/entities/email-template.entity.js +0 -136
  73. package/cjs/entities/index.js +0 -56
  74. package/cjs/entities/index.spec.js +0 -18
  75. package/cjs/enums/email-provider-type.enum.js +0 -18
  76. package/cjs/enums/index.js +0 -18
  77. package/cjs/index.js +0 -28
  78. package/cjs/interfaces/email-config.interface.js +0 -4
  79. package/cjs/interfaces/email-module-options.interface.js +0 -4
  80. package/cjs/interfaces/email-provider.interface.js +0 -4
  81. package/cjs/interfaces/email-template.interface.js +0 -4
  82. package/cjs/interfaces/index.js +0 -21
  83. package/cjs/modules/email.module.js +0 -122
  84. package/cjs/modules/email.module.spec.js +0 -142
  85. package/cjs/modules/index.js +0 -18
  86. package/cjs/providers/email-factory.service.js +0 -113
  87. package/cjs/providers/email-factory.service.spec.js +0 -242
  88. package/cjs/providers/email-provider.registry.js +0 -41
  89. package/cjs/providers/email-provider.registry.spec.js +0 -58
  90. package/cjs/providers/index.js +0 -22
  91. package/cjs/providers/mailgun-provider.js +0 -119
  92. package/cjs/providers/mailgun-provider.spec.js +0 -244
  93. package/cjs/providers/sendgrid-provider.js +0 -129
  94. package/cjs/providers/sendgrid-provider.spec.js +0 -346
  95. package/cjs/providers/smtp-provider.js +0 -164
  96. package/cjs/providers/smtp-provider.spec.js +0 -327
  97. package/cjs/services/email-config.service.js +0 -71
  98. package/cjs/services/email-config.service.spec.js +0 -69
  99. package/cjs/services/email-datasource.provider.js +0 -84
  100. package/cjs/services/email-datasource.provider.spec.js +0 -135
  101. package/cjs/services/email-provider-config.service.js +0 -159
  102. package/cjs/services/email-provider-config.service.spec.js +0 -269
  103. package/cjs/services/email-send.service.js +0 -315
  104. package/cjs/services/email-send.service.spec.js +0 -614
  105. package/cjs/services/email-template.service.js +0 -144
  106. package/cjs/services/email-template.service.spec.js +0 -261
  107. package/cjs/services/index.js +0 -22
  108. package/cjs/utils/email-templates.util.js +0 -64
  109. package/cjs/utils/email-templates.util.spec.js +0 -61
  110. package/cjs/utils/index.js +0 -18
  111. package/fesm/config/email.constants.js +0 -4
  112. package/fesm/config/event-actions.js +0 -13
  113. package/fesm/config/message-keys.js +0 -18
  114. package/fesm/controllers/email-config.controller.js +0 -101
  115. package/fesm/controllers/email-config.controller.spec.js +0 -81
  116. package/fesm/controllers/email-send.controller.js +0 -118
  117. package/fesm/controllers/email-send.controller.spec.js +0 -135
  118. package/fesm/controllers/email-template.controller.js +0 -132
  119. package/fesm/controllers/email-template.controller.spec.js +0 -85
  120. package/fesm/docs/email-swagger.config.js +0 -109
  121. package/fesm/docs/email-swagger.config.spec.js +0 -58
  122. package/fesm/dtos/email-config.dto.js +0 -191
  123. package/fesm/dtos/email-send.dto.js +0 -324
  124. package/fesm/dtos/email-template.dto.js +0 -231
  125. package/fesm/entities/email-config-with-company.entity.js +0 -53
  126. package/fesm/entities/email-config.entity.js +0 -104
  127. package/fesm/entities/email-template-with-company.entity.js +0 -55
  128. package/fesm/entities/email-template.entity.js +0 -126
  129. package/fesm/entities/index.spec.js +0 -14
  130. package/fesm/enums/email-provider-type.enum.js +0 -8
  131. package/fesm/interfaces/email-config.interface.js +0 -1
  132. package/fesm/interfaces/email-module-options.interface.js +0 -1
  133. package/fesm/interfaces/email-provider.interface.js +0 -1
  134. package/fesm/interfaces/email-template.interface.js +0 -1
  135. package/fesm/modules/email.module.js +0 -112
  136. package/fesm/modules/email.module.spec.js +0 -138
  137. package/fesm/providers/email-factory.service.js +0 -103
  138. package/fesm/providers/email-factory.service.spec.js +0 -238
  139. package/fesm/providers/email-provider.registry.js +0 -31
  140. package/fesm/providers/email-provider.registry.spec.js +0 -54
  141. package/fesm/providers/mailgun-provider.js +0 -109
  142. package/fesm/providers/mailgun-provider.spec.js +0 -240
  143. package/fesm/providers/sendgrid-provider.js +0 -119
  144. package/fesm/providers/sendgrid-provider.spec.js +0 -342
  145. package/fesm/providers/smtp-provider.js +0 -113
  146. package/fesm/providers/smtp-provider.spec.js +0 -282
  147. package/fesm/services/email-config.service.js +0 -61
  148. package/fesm/services/email-config.service.spec.js +0 -65
  149. package/fesm/services/email-datasource.provider.js +0 -74
  150. package/fesm/services/email-datasource.provider.spec.js +0 -131
  151. package/fesm/services/email-provider-config.service.js +0 -149
  152. package/fesm/services/email-provider-config.service.spec.js +0 -265
  153. package/fesm/services/email-send.service.js +0 -305
  154. package/fesm/services/email-send.service.spec.js +0 -610
  155. package/fesm/services/email-template.service.js +0 -134
  156. package/fesm/services/email-template.service.spec.js +0 -257
  157. package/fesm/utils/email-templates.util.js +0 -47
  158. package/fesm/utils/email-templates.util.spec.js +0 -57
  159. package/fesm/utils/index.js +0 -1
@@ -1,282 +0,0 @@
1
- const mockTransporter = {
2
- verify: jest.fn(),
3
- sendMail: jest.fn(),
4
- close: jest.fn()
5
- };
6
- jest.mock('nodemailer', ()=>({
7
- createTransport: jest.fn()
8
- }));
9
- import * as nodemailer from 'nodemailer';
10
- import { SmtpProvider } from './smtp-provider';
11
- const createTransport = nodemailer.createTransport;
12
- createTransport.mockReturnValue(mockTransporter);
13
- function injectTransporter(provider, transporter) {
14
- provider.transporter = transporter;
15
- }
16
- describe('SmtpProvider', ()=>{
17
- let provider;
18
- beforeEach(()=>{
19
- provider = new SmtpProvider();
20
- jest.clearAllMocks();
21
- });
22
- describe('initialize', ()=>{
23
- it('creates a nodemailer transport with the given config and verifies the connection', async ()=>{
24
- mockTransporter.verify.mockResolvedValue(true);
25
- await provider.initialize({
26
- host: 'smtp.example.com',
27
- port: 587,
28
- auth: {
29
- user: 'user',
30
- pass: 'pass'
31
- }
32
- });
33
- expect(createTransport).toHaveBeenCalledWith(expect.objectContaining({
34
- host: 'smtp.example.com',
35
- port: 587,
36
- secure: false,
37
- auth: {
38
- user: 'user',
39
- pass: 'pass'
40
- },
41
- tls: expect.objectContaining({
42
- rejectUnauthorized: true,
43
- minVersion: 'TLSv1.2'
44
- })
45
- }));
46
- expect(mockTransporter.verify).toHaveBeenCalled();
47
- });
48
- it('defaults secure=true when port is 465 and secure is not explicitly set', async ()=>{
49
- mockTransporter.verify.mockResolvedValue(true);
50
- await provider.initialize({
51
- host: 'smtp.example.com',
52
- port: 465
53
- });
54
- expect(createTransport).toHaveBeenCalledWith(expect.objectContaining({
55
- secure: true
56
- }));
57
- });
58
- it('honors an explicit secure flag over the port-based default', async ()=>{
59
- mockTransporter.verify.mockResolvedValue(true);
60
- await provider.initialize({
61
- host: 'smtp.example.com',
62
- port: 587,
63
- secure: true
64
- });
65
- expect(createTransport).toHaveBeenCalledWith(expect.objectContaining({
66
- secure: true
67
- }));
68
- });
69
- it('allows disabling TLS certificate validation via config', async ()=>{
70
- mockTransporter.verify.mockResolvedValue(true);
71
- await provider.initialize({
72
- host: 'smtp.example.com',
73
- port: 587,
74
- tls: {
75
- rejectUnauthorized: false
76
- }
77
- });
78
- expect(createTransport).toHaveBeenCalledWith(expect.objectContaining({
79
- tls: expect.objectContaining({
80
- rejectUnauthorized: false
81
- })
82
- }));
83
- });
84
- it('propagates a connection verification failure', async ()=>{
85
- mockTransporter.verify.mockRejectedValue(new Error('ECONNREFUSED'));
86
- await expect(provider.initialize({
87
- host: 'smtp.example.com',
88
- port: 587
89
- })).rejects.toThrow('ECONNREFUSED');
90
- });
91
- });
92
- describe('sendEmail', ()=>{
93
- it('returns a failure result when the provider was never initialized', async ()=>{
94
- const result = await provider.sendEmail({
95
- to: 'a@example.com',
96
- subject: 'Hi'
97
- });
98
- expect(result).toEqual({
99
- success: false,
100
- error: 'SMTP provider not initialized'
101
- });
102
- });
103
- it('sends mail via the transporter and returns the message id', async ()=>{
104
- injectTransporter(provider, mockTransporter);
105
- mockTransporter.sendMail.mockResolvedValue({
106
- messageId: 'msg-1'
107
- });
108
- const result = await provider.sendEmail({
109
- to: [
110
- 'a@example.com',
111
- 'b@example.com'
112
- ],
113
- cc: 'c@example.com',
114
- bcc: [
115
- 'd@example.com'
116
- ],
117
- subject: 'Hello',
118
- html: '<p>hi</p>',
119
- text: 'hi',
120
- from: 'sender@example.com',
121
- fromName: 'Sender Name',
122
- replyTo: 'reply@example.com'
123
- });
124
- expect(result).toEqual({
125
- success: true,
126
- messageId: 'msg-1'
127
- });
128
- expect(mockTransporter.sendMail).toHaveBeenCalledWith(expect.objectContaining({
129
- from: '"Sender Name" <sender@example.com>',
130
- to: 'a@example.com, b@example.com',
131
- cc: 'c@example.com',
132
- bcc: 'd@example.com',
133
- subject: 'Hello',
134
- html: '<p>hi</p>',
135
- text: 'hi',
136
- replyTo: 'reply@example.com'
137
- }));
138
- });
139
- it('sends the bare "from" address when fromName is not provided', async ()=>{
140
- injectTransporter(provider, mockTransporter);
141
- mockTransporter.sendMail.mockResolvedValue({
142
- messageId: 'msg-1'
143
- });
144
- await provider.sendEmail({
145
- to: 'a@example.com',
146
- subject: 'Hi',
147
- from: 'sender@example.com'
148
- });
149
- expect(mockTransporter.sendMail).toHaveBeenCalledWith(expect.objectContaining({
150
- from: 'sender@example.com'
151
- }));
152
- });
153
- it('maps attachments through to nodemailer mail options', async ()=>{
154
- injectTransporter(provider, mockTransporter);
155
- mockTransporter.sendMail.mockResolvedValue({
156
- messageId: 'msg-1'
157
- });
158
- await provider.sendEmail({
159
- to: 'a@example.com',
160
- subject: 'Hi',
161
- attachments: [
162
- {
163
- filename: 'doc.txt',
164
- content: Buffer.from('hi'),
165
- contentType: 'text/plain'
166
- }
167
- ]
168
- });
169
- const [mailOptions] = mockTransporter.sendMail.mock.calls[0];
170
- expect(mailOptions.attachments).toEqual([
171
- {
172
- filename: 'doc.txt',
173
- content: Buffer.from('hi'),
174
- contentType: 'text/plain',
175
- encoding: undefined
176
- }
177
- ]);
178
- });
179
- it('returns the error message when sendMail rejects with an Error', async ()=>{
180
- injectTransporter(provider, mockTransporter);
181
- mockTransporter.sendMail.mockRejectedValue(new Error('Connection refused'));
182
- const result = await provider.sendEmail({
183
- to: 'a@example.com',
184
- subject: 'Hi'
185
- });
186
- expect(result).toEqual({
187
- success: false,
188
- error: 'Connection refused'
189
- });
190
- });
191
- it('returns "Unknown error" when sendMail rejects with a non-Error', async ()=>{
192
- injectTransporter(provider, mockTransporter);
193
- mockTransporter.sendMail.mockRejectedValue('raw string failure');
194
- const result = await provider.sendEmail({
195
- to: 'a@example.com',
196
- subject: 'Hi'
197
- });
198
- expect(result).toEqual({
199
- success: false,
200
- error: 'Unknown error'
201
- });
202
- });
203
- it('surfaces a timeout error if sendMail never settles before the send timeout', async ()=>{
204
- jest.useFakeTimers();
205
- injectTransporter(provider, mockTransporter);
206
- mockTransporter.sendMail.mockReturnValue(new Promise(()=>{}));
207
- const pending = provider.sendEmail({
208
- to: 'a@example.com',
209
- subject: 'Hi'
210
- });
211
- jest.advanceTimersByTime(30_000);
212
- const result = await pending;
213
- expect(result).toEqual({
214
- success: false,
215
- error: 'SMTP send timeout'
216
- });
217
- jest.useRealTimers();
218
- });
219
- });
220
- describe('sendBulkEmails', ()=>{
221
- it('sends each message individually and aggregates the results', async ()=>{
222
- injectTransporter(provider, mockTransporter);
223
- mockTransporter.sendMail.mockResolvedValueOnce({
224
- messageId: 'id-1'
225
- }).mockRejectedValueOnce(new Error('failed-2'));
226
- const results = await provider.sendBulkEmails([
227
- {
228
- to: 'a@example.com',
229
- subject: 'A'
230
- },
231
- {
232
- to: 'b@example.com',
233
- subject: 'B'
234
- }
235
- ]);
236
- expect(results).toEqual([
237
- {
238
- success: true,
239
- messageId: 'id-1'
240
- },
241
- {
242
- success: false,
243
- error: 'failed-2'
244
- }
245
- ]);
246
- });
247
- });
248
- describe('healthCheck', ()=>{
249
- it('returns false when not initialized', async ()=>{
250
- await expect(provider.healthCheck()).resolves.toBe(false);
251
- });
252
- it('returns true when transporter.verify() resolves', async ()=>{
253
- injectTransporter(provider, mockTransporter);
254
- mockTransporter.verify.mockResolvedValue(true);
255
- await expect(provider.healthCheck()).resolves.toBe(true);
256
- });
257
- it('returns false when transporter.verify() rejects', async ()=>{
258
- injectTransporter(provider, mockTransporter);
259
- mockTransporter.verify.mockRejectedValue(new Error('down'));
260
- await expect(provider.healthCheck()).resolves.toBe(false);
261
- });
262
- });
263
- describe('close', ()=>{
264
- it('closes and clears the transporter', async ()=>{
265
- injectTransporter(provider, mockTransporter);
266
- await provider.close();
267
- expect(mockTransporter.close).toHaveBeenCalled();
268
- const result = await provider.sendEmail({
269
- to: 'a@example.com',
270
- subject: 'Hi'
271
- });
272
- expect(result).toEqual({
273
- success: false,
274
- error: 'SMTP provider not initialized'
275
- });
276
- });
277
- it('is a no-op when no transporter exists', async ()=>{
278
- await expect(provider.close()).resolves.toBeUndefined();
279
- expect(mockTransporter.close).not.toHaveBeenCalled();
280
- });
281
- });
282
- });
@@ -1,61 +0,0 @@
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 { Inject, Injectable } from '@nestjs/common';
29
- import { EMAIL_MODULE_OPTIONS, DEFAULT_FROM_NAME } from '../config/email.constants';
30
- import { EmailModuleOptions } from '../interfaces';
31
- export class EmailConfigService {
32
- // ─── IModuleConfigService Implementation ────────────────────────────────────
33
- isCompanyFeatureEnabled() {
34
- return this.options.bootstrapAppConfig?.enableCompanyFeature ?? false;
35
- }
36
- getDatabaseMode() {
37
- return this.options.bootstrapAppConfig?.databaseMode ?? 'single';
38
- }
39
- isMultiTenant() {
40
- return this.getDatabaseMode() === 'multi-tenant';
41
- }
42
- // ─── Config Getters ─────────────────────────────────────────────────────────
43
- getDefaultFromName() {
44
- return DEFAULT_FROM_NAME;
45
- }
46
- getOptions() {
47
- return this.options;
48
- }
49
- constructor(options){
50
- _define_property(this, "options", void 0);
51
- this.options = options;
52
- }
53
- }
54
- EmailConfigService = _ts_decorate([
55
- Injectable(),
56
- _ts_param(0, Inject(EMAIL_MODULE_OPTIONS)),
57
- _ts_metadata("design:type", Function),
58
- _ts_metadata("design:paramtypes", [
59
- typeof EmailModuleOptions === "undefined" ? Object : EmailModuleOptions
60
- ])
61
- ], EmailConfigService);
@@ -1,65 +0,0 @@
1
- import { DEFAULT_FROM_NAME } from '../config/email.constants';
2
- import { EmailConfigService } from './email-config.service';
3
- function buildOptions(overrides = {}) {
4
- return {
5
- bootstrapAppConfig: undefined,
6
- config: undefined,
7
- ...overrides
8
- };
9
- }
10
- describe('EmailConfigService', ()=>{
11
- describe('isCompanyFeatureEnabled', ()=>{
12
- it('returns false when bootstrapAppConfig is not provided', ()=>{
13
- const service = new EmailConfigService(buildOptions());
14
- expect(service.isCompanyFeatureEnabled()).toBe(false);
15
- });
16
- it('returns false when enableCompanyFeature is explicitly false', ()=>{
17
- const service = new EmailConfigService(buildOptions({
18
- bootstrapAppConfig: {
19
- enableCompanyFeature: false
20
- }
21
- }));
22
- expect(service.isCompanyFeatureEnabled()).toBe(false);
23
- });
24
- it('returns true when enableCompanyFeature is true', ()=>{
25
- const service = new EmailConfigService(buildOptions({
26
- bootstrapAppConfig: {
27
- enableCompanyFeature: true
28
- }
29
- }));
30
- expect(service.isCompanyFeatureEnabled()).toBe(true);
31
- });
32
- });
33
- describe('getDatabaseMode / isMultiTenant', ()=>{
34
- it('defaults to "single" when bootstrapAppConfig is missing', ()=>{
35
- const service = new EmailConfigService(buildOptions());
36
- expect(service.getDatabaseMode()).toBe('single');
37
- expect(service.isMultiTenant()).toBe(false);
38
- });
39
- it('reflects "multi-tenant" mode from bootstrapAppConfig', ()=>{
40
- const service = new EmailConfigService(buildOptions({
41
- bootstrapAppConfig: {
42
- databaseMode: 'multi-tenant'
43
- }
44
- }));
45
- expect(service.getDatabaseMode()).toBe('multi-tenant');
46
- expect(service.isMultiTenant()).toBe(true);
47
- });
48
- });
49
- describe('getDefaultFromName', ()=>{
50
- it('always returns the module-level DEFAULT_FROM_NAME constant', ()=>{
51
- const service = new EmailConfigService(buildOptions());
52
- expect(service.getDefaultFromName()).toBe(DEFAULT_FROM_NAME);
53
- expect(service.getDefaultFromName()).toBe('FLUSYS');
54
- });
55
- });
56
- describe('getOptions', ()=>{
57
- it('returns the exact options object injected via the constructor', ()=>{
58
- const options = buildOptions({
59
- defaultProvider: 'smtp'
60
- });
61
- const service = new EmailConfigService(options);
62
- expect(service.getOptions()).toBe(options);
63
- });
64
- });
65
- });
@@ -1,74 +0,0 @@
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, Optional, Scope } from '@nestjs/common';
30
- import { REQUEST } from '@nestjs/core';
31
- import { Request } from 'express';
32
- import { getEmailEntitiesByConfig } from '../entities';
33
- import { EmailConfigService } from './email-config.service';
34
- export class EmailDataSourceProvider extends MultiTenantDataSourceService {
35
- static buildParentOptions(options) {
36
- return {
37
- bootstrapAppConfig: options.bootstrapAppConfig,
38
- defaultDatabaseConfig: options.config?.defaultDatabaseConfig,
39
- tenantDefaultDatabaseConfig: options.config?.tenantDefaultDatabaseConfig,
40
- tenants: options.config?.tenants
41
- };
42
- }
43
- getEnableCompanyFeatureForTenant(tenant) {
44
- return tenant?.enableCompanyFeature ?? this.configService.isCompanyFeatureEnabled();
45
- }
46
- async createDataSourceFromConfig(config) {
47
- const currentTenant = this.getCurrentTenant();
48
- const enableCompanyFeature = this.getEnableCompanyFeatureForTenant(currentTenant ?? undefined);
49
- const entities = getEmailEntitiesByConfig(enableCompanyFeature);
50
- return super.createDataSourceFromConfig(config, entities);
51
- }
52
- constructor(configService, request){
53
- super(EmailDataSourceProvider.buildParentOptions(configService.getOptions()), request), _define_property(this, "configService", void 0), this.configService = configService;
54
- }
55
- }
56
- _define_property(EmailDataSourceProvider, "tenantConnections", new Map());
57
- _define_property(EmailDataSourceProvider, "singleDataSource", null);
58
- _define_property(EmailDataSourceProvider, "tenantsRegistry", new Map());
59
- _define_property(EmailDataSourceProvider, "initialized", false);
60
- _define_property(EmailDataSourceProvider, "connectionLocks", new Map());
61
- _define_property(EmailDataSourceProvider, "singleConnectionLock", null);
62
- EmailDataSourceProvider = _ts_decorate([
63
- Injectable({
64
- scope: Scope.REQUEST
65
- }),
66
- _ts_param(0, Inject(EmailConfigService)),
67
- _ts_param(1, Optional()),
68
- _ts_param(1, Inject(REQUEST)),
69
- _ts_metadata("design:type", Function),
70
- _ts_metadata("design:paramtypes", [
71
- typeof EmailConfigService === "undefined" ? Object : EmailConfigService,
72
- typeof Request === "undefined" ? Object : Request
73
- ])
74
- ], EmailDataSourceProvider);
@@ -1,131 +0,0 @@
1
- import { MultiTenantDataSourceService } from '@flusys/nestjs-shared/modules';
2
- import { EmailCompanyEntities, EmailCoreEntities } from '../entities';
3
- import { EmailDataSourceProvider } from './email-datasource.provider';
4
- function buildConfigService(overrides = {}) {
5
- const options = {
6
- bootstrapAppConfig: {
7
- databaseMode: 'single',
8
- enableCompanyFeature: false
9
- },
10
- config: {
11
- defaultDatabaseConfig: {
12
- type: 'mysql',
13
- host: 'localhost',
14
- database: 'flusys'
15
- },
16
- tenants: []
17
- },
18
- ...overrides
19
- };
20
- return {
21
- isCompanyFeatureEnabled: jest.fn().mockReturnValue(options.bootstrapAppConfig?.enableCompanyFeature ?? false),
22
- getDatabaseMode: jest.fn().mockReturnValue(options.bootstrapAppConfig?.databaseMode ?? 'single'),
23
- isMultiTenant: jest.fn().mockReturnValue(options.bootstrapAppConfig?.databaseMode === 'multi-tenant'),
24
- getDefaultFromName: jest.fn().mockReturnValue('FLUSYS'),
25
- getOptions: jest.fn().mockReturnValue(options)
26
- };
27
- }
28
- describe('EmailDataSourceProvider', ()=>{
29
- afterEach(()=>{
30
- jest.restoreAllMocks();
31
- // Reset static state isolated to this subclass between tests.
32
- EmailDataSourceProvider.singleDataSource = null;
33
- EmailDataSourceProvider.initialized = false;
34
- EmailDataSourceProvider.tenantsRegistry.clear();
35
- });
36
- it('propagates bootstrapAppConfig from EmailConfigService into the base data source service', ()=>{
37
- const configService = buildConfigService({
38
- bootstrapAppConfig: {
39
- databaseMode: 'multi-tenant',
40
- enableCompanyFeature: true
41
- }
42
- });
43
- const provider = new EmailDataSourceProvider(configService);
44
- expect(provider.isMultiTenant()).toBe(true);
45
- expect(provider.getDatabaseMode()).toBe('multi-tenant');
46
- });
47
- it('defaults to single-database mode when bootstrapAppConfig is absent', ()=>{
48
- const configService = buildConfigService({
49
- bootstrapAppConfig: undefined
50
- });
51
- const provider = new EmailDataSourceProvider(configService);
52
- expect(provider.isMultiTenant()).toBe(false);
53
- expect(provider.getDatabaseMode()).toBe('single');
54
- });
55
- describe('getEnableCompanyFeatureForTenant', ()=>{
56
- it('falls back to EmailConfigService.isCompanyFeatureEnabled() when no tenant is given', ()=>{
57
- const configService = buildConfigService({
58
- bootstrapAppConfig: {
59
- databaseMode: 'single',
60
- enableCompanyFeature: true
61
- }
62
- });
63
- const provider = new EmailDataSourceProvider(configService);
64
- expect(provider.getEnableCompanyFeatureForTenant(undefined)).toBe(true);
65
- });
66
- it('prefers the tenant-level enableCompanyFeature flag over the module default', ()=>{
67
- const configService = buildConfigService({
68
- bootstrapAppConfig: {
69
- databaseMode: 'multi-tenant',
70
- enableCompanyFeature: true
71
- }
72
- });
73
- const provider = new EmailDataSourceProvider(configService);
74
- expect(provider.getEnableCompanyFeatureForTenant({
75
- id: 'tenant-1',
76
- database: 'tenant_db',
77
- enableCompanyFeature: false
78
- })).toBe(false);
79
- });
80
- it('uses the module default when the tenant does not specify enableCompanyFeature', ()=>{
81
- const configService = buildConfigService({
82
- bootstrapAppConfig: {
83
- databaseMode: 'multi-tenant',
84
- enableCompanyFeature: true
85
- }
86
- });
87
- const provider = new EmailDataSourceProvider(configService);
88
- expect(provider.getEnableCompanyFeatureForTenant({
89
- id: 'tenant-1',
90
- database: 'tenant_db'
91
- })).toBe(true);
92
- });
93
- });
94
- describe('createDataSourceFromConfig', ()=>{
95
- it('resolves core (non-company) entities when the company feature is disabled', async ()=>{
96
- const configService = buildConfigService({
97
- bootstrapAppConfig: {
98
- databaseMode: 'single',
99
- enableCompanyFeature: false
100
- }
101
- });
102
- const provider = new EmailDataSourceProvider(configService);
103
- const fakeDataSource = {};
104
- const superSpy = jest.spyOn(MultiTenantDataSourceService.prototype, 'createDataSourceFromConfig').mockResolvedValue(fakeDataSource);
105
- const config = {
106
- type: 'mysql',
107
- database: 'flusys'
108
- };
109
- const result = await provider.createDataSourceFromConfig(config);
110
- expect(result).toBe(fakeDataSource);
111
- expect(superSpy).toHaveBeenCalledWith(config, EmailCoreEntities);
112
- });
113
- it('resolves company-aware entities when the company feature is enabled', async ()=>{
114
- const configService = buildConfigService({
115
- bootstrapAppConfig: {
116
- databaseMode: 'single',
117
- enableCompanyFeature: true
118
- }
119
- });
120
- const provider = new EmailDataSourceProvider(configService);
121
- const fakeDataSource = {};
122
- const superSpy = jest.spyOn(MultiTenantDataSourceService.prototype, 'createDataSourceFromConfig').mockResolvedValue(fakeDataSource);
123
- const config = {
124
- type: 'mysql',
125
- database: 'flusys'
126
- };
127
- await provider.createDataSourceFromConfig(config);
128
- expect(superSpy).toHaveBeenCalledWith(config, EmailCompanyEntities);
129
- });
130
- });
131
- });