@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.
- package/config/index.d.ts +3 -3
- package/controllers/email-config.controller.d.ts +2 -2
- package/controllers/email-send.controller.d.ts +3 -3
- package/controllers/email-template.controller.d.ts +4 -4
- package/controllers/index.d.ts +3 -3
- package/docs/email-swagger.config.d.ts +2 -2
- package/docs/index.d.ts +1 -1
- package/dtos/email-config.dto.d.ts +1 -1
- package/dtos/email-send.dto.d.ts +1 -1
- package/dtos/index.d.ts +3 -3
- package/entities/email-config-with-company.entity.d.ts +1 -1
- package/entities/email-template-with-company.entity.d.ts +1 -1
- package/entities/index.d.ts +10 -10
- package/enums/index.d.ts +1 -1
- package/fesm/261.js +799 -0
- package/fesm/493.js +1723 -0
- package/fesm/chunks/99.js +12 -0
- package/fesm/config/index.js +104 -3
- package/fesm/controllers/index.js +396 -3
- package/fesm/docs/index.js +134 -1
- package/fesm/dtos/index.js +22 -3
- package/fesm/entities/index.js +409 -13
- package/fesm/enums/index.js +37 -1
- package/fesm/index.js +762 -10
- package/fesm/interfaces/index.js +18 -4
- package/fesm/modules/index.js +541 -1
- package/fesm/providers/index.js +523 -5
- package/fesm/runtime.js +134 -0
- package/fesm/services/index.js +17 -5
- package/index.d.ts +10 -10
- package/interfaces/email-config.interface.d.ts +2 -2
- package/interfaces/email-module-options.interface.d.ts +3 -3
- package/interfaces/email-template.interface.d.ts +1 -1
- package/interfaces/index.d.ts +4 -4
- package/modules/email.module.d.ts +2 -2
- package/modules/index.d.ts +1 -1
- package/package.json +27 -38
- package/providers/email-factory.service.d.ts +2 -2
- package/providers/email-provider.registry.d.ts +2 -2
- package/providers/index.d.ts +5 -5
- package/providers/mailgun-provider.d.ts +1 -1
- package/providers/sendgrid-provider.d.ts +1 -1
- package/providers/smtp-provider.d.ts +1 -1
- package/services/email-config.service.d.ts +3 -3
- package/services/email-datasource.provider.d.ts +3 -3
- package/services/email-provider-config.service.d.ts +7 -7
- package/services/email-send.service.d.ts +7 -7
- package/services/email-template.service.d.ts +6 -6
- package/services/index.d.ts +5 -5
- package/utils/index.d.ts +1 -1
- package/cjs/config/email.constants.js +0 -22
- package/cjs/config/event-actions.js +0 -34
- package/cjs/config/index.js +0 -20
- package/cjs/config/message-keys.js +0 -39
- package/cjs/controllers/email-config.controller.js +0 -111
- package/cjs/controllers/email-config.controller.spec.js +0 -85
- package/cjs/controllers/email-send.controller.js +0 -128
- package/cjs/controllers/email-send.controller.spec.js +0 -139
- package/cjs/controllers/email-template.controller.js +0 -142
- package/cjs/controllers/email-template.controller.spec.js +0 -89
- package/cjs/controllers/index.js +0 -20
- package/cjs/docs/email-swagger.config.js +0 -116
- package/cjs/docs/email-swagger.config.spec.js +0 -62
- package/cjs/docs/index.js +0 -11
- package/cjs/dtos/email-config.dto.js +0 -212
- package/cjs/dtos/email-send.dto.js +0 -354
- package/cjs/dtos/email-template.dto.js +0 -252
- package/cjs/dtos/index.js +0 -20
- package/cjs/entities/email-config-with-company.entity.js +0 -63
- package/cjs/entities/email-config.entity.js +0 -114
- package/cjs/entities/email-template-with-company.entity.js +0 -65
- package/cjs/entities/email-template.entity.js +0 -136
- package/cjs/entities/index.js +0 -56
- package/cjs/entities/index.spec.js +0 -18
- package/cjs/enums/email-provider-type.enum.js +0 -18
- package/cjs/enums/index.js +0 -18
- package/cjs/index.js +0 -28
- package/cjs/interfaces/email-config.interface.js +0 -4
- package/cjs/interfaces/email-module-options.interface.js +0 -4
- package/cjs/interfaces/email-provider.interface.js +0 -4
- package/cjs/interfaces/email-template.interface.js +0 -4
- package/cjs/interfaces/index.js +0 -21
- package/cjs/modules/email.module.js +0 -122
- package/cjs/modules/email.module.spec.js +0 -142
- package/cjs/modules/index.js +0 -18
- package/cjs/providers/email-factory.service.js +0 -113
- package/cjs/providers/email-factory.service.spec.js +0 -242
- package/cjs/providers/email-provider.registry.js +0 -41
- package/cjs/providers/email-provider.registry.spec.js +0 -58
- package/cjs/providers/index.js +0 -22
- package/cjs/providers/mailgun-provider.js +0 -119
- package/cjs/providers/mailgun-provider.spec.js +0 -244
- package/cjs/providers/sendgrid-provider.js +0 -129
- package/cjs/providers/sendgrid-provider.spec.js +0 -346
- package/cjs/providers/smtp-provider.js +0 -164
- package/cjs/providers/smtp-provider.spec.js +0 -327
- package/cjs/services/email-config.service.js +0 -71
- package/cjs/services/email-config.service.spec.js +0 -69
- package/cjs/services/email-datasource.provider.js +0 -84
- package/cjs/services/email-datasource.provider.spec.js +0 -135
- package/cjs/services/email-provider-config.service.js +0 -159
- package/cjs/services/email-provider-config.service.spec.js +0 -269
- package/cjs/services/email-send.service.js +0 -315
- package/cjs/services/email-send.service.spec.js +0 -614
- package/cjs/services/email-template.service.js +0 -144
- package/cjs/services/email-template.service.spec.js +0 -261
- package/cjs/services/index.js +0 -22
- package/cjs/utils/email-templates.util.js +0 -64
- package/cjs/utils/email-templates.util.spec.js +0 -61
- package/cjs/utils/index.js +0 -18
- package/fesm/config/email.constants.js +0 -4
- package/fesm/config/event-actions.js +0 -13
- package/fesm/config/message-keys.js +0 -18
- package/fesm/controllers/email-config.controller.js +0 -101
- package/fesm/controllers/email-config.controller.spec.js +0 -81
- package/fesm/controllers/email-send.controller.js +0 -118
- package/fesm/controllers/email-send.controller.spec.js +0 -135
- package/fesm/controllers/email-template.controller.js +0 -132
- package/fesm/controllers/email-template.controller.spec.js +0 -85
- package/fesm/docs/email-swagger.config.js +0 -109
- package/fesm/docs/email-swagger.config.spec.js +0 -58
- package/fesm/dtos/email-config.dto.js +0 -191
- package/fesm/dtos/email-send.dto.js +0 -324
- package/fesm/dtos/email-template.dto.js +0 -231
- package/fesm/entities/email-config-with-company.entity.js +0 -53
- package/fesm/entities/email-config.entity.js +0 -104
- package/fesm/entities/email-template-with-company.entity.js +0 -55
- package/fesm/entities/email-template.entity.js +0 -126
- package/fesm/entities/index.spec.js +0 -14
- package/fesm/enums/email-provider-type.enum.js +0 -8
- package/fesm/interfaces/email-config.interface.js +0 -1
- package/fesm/interfaces/email-module-options.interface.js +0 -1
- package/fesm/interfaces/email-provider.interface.js +0 -1
- package/fesm/interfaces/email-template.interface.js +0 -1
- package/fesm/modules/email.module.js +0 -112
- package/fesm/modules/email.module.spec.js +0 -138
- package/fesm/providers/email-factory.service.js +0 -103
- package/fesm/providers/email-factory.service.spec.js +0 -238
- package/fesm/providers/email-provider.registry.js +0 -31
- package/fesm/providers/email-provider.registry.spec.js +0 -54
- package/fesm/providers/mailgun-provider.js +0 -109
- package/fesm/providers/mailgun-provider.spec.js +0 -240
- package/fesm/providers/sendgrid-provider.js +0 -119
- package/fesm/providers/sendgrid-provider.spec.js +0 -342
- package/fesm/providers/smtp-provider.js +0 -113
- package/fesm/providers/smtp-provider.spec.js +0 -282
- package/fesm/services/email-config.service.js +0 -61
- package/fesm/services/email-config.service.spec.js +0 -65
- package/fesm/services/email-datasource.provider.js +0 -74
- package/fesm/services/email-datasource.provider.spec.js +0 -131
- package/fesm/services/email-provider-config.service.js +0 -149
- package/fesm/services/email-provider-config.service.spec.js +0 -265
- package/fesm/services/email-send.service.js +0 -305
- package/fesm/services/email-send.service.spec.js +0 -610
- package/fesm/services/email-template.service.js +0 -134
- package/fesm/services/email-template.service.spec.js +0 -257
- package/fesm/utils/email-templates.util.js +0 -47
- package/fesm/utils/email-templates.util.spec.js +0 -57
- package/fesm/utils/index.js +0 -1
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", {
|
|
3
|
-
value: true
|
|
4
|
-
});
|
|
5
|
-
Object.defineProperty(exports, "EmailTemplateService", {
|
|
6
|
-
enumerable: true,
|
|
7
|
-
get: function() {
|
|
8
|
-
return EmailTemplateService;
|
|
9
|
-
}
|
|
10
|
-
});
|
|
11
|
-
const _classes = require("@flusys/nestjs-shared/classes");
|
|
12
|
-
const _modules = require("@flusys/nestjs-shared/modules");
|
|
13
|
-
const _utils = require("@flusys/nestjs-shared/utils");
|
|
14
|
-
const _common = require("@nestjs/common");
|
|
15
|
-
const _config = require("../config");
|
|
16
|
-
const _entities = require("../entities");
|
|
17
|
-
const _emailconfigservice = require("./email-config.service");
|
|
18
|
-
const _emaildatasourceprovider = require("./email-datasource.provider");
|
|
19
|
-
function _define_property(obj, key, value) {
|
|
20
|
-
if (key in obj) {
|
|
21
|
-
Object.defineProperty(obj, key, {
|
|
22
|
-
value: value,
|
|
23
|
-
enumerable: true,
|
|
24
|
-
configurable: true,
|
|
25
|
-
writable: true
|
|
26
|
-
});
|
|
27
|
-
} else {
|
|
28
|
-
obj[key] = value;
|
|
29
|
-
}
|
|
30
|
-
return obj;
|
|
31
|
-
}
|
|
32
|
-
function _ts_decorate(decorators, target, key, desc) {
|
|
33
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
34
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
35
|
-
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;
|
|
36
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
37
|
-
}
|
|
38
|
-
function _ts_metadata(k, v) {
|
|
39
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
40
|
-
}
|
|
41
|
-
function _ts_param(paramIndex, decorator) {
|
|
42
|
-
return function(target, key) {
|
|
43
|
-
decorator(target, key, paramIndex);
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
const DEFAULT_SELECT_FIELDS = [
|
|
47
|
-
'id',
|
|
48
|
-
'name',
|
|
49
|
-
'slug',
|
|
50
|
-
'description',
|
|
51
|
-
'subject',
|
|
52
|
-
'schema',
|
|
53
|
-
'htmlContent',
|
|
54
|
-
'textContent',
|
|
55
|
-
'schemaVersion',
|
|
56
|
-
'isActive',
|
|
57
|
-
'isHtml',
|
|
58
|
-
'createdAt',
|
|
59
|
-
'updatedAt'
|
|
60
|
-
];
|
|
61
|
-
let EmailTemplateService = class EmailTemplateService extends _classes.ApiService {
|
|
62
|
-
async convertSingleDtoToEntity(dto, user) {
|
|
63
|
-
const entity = await super.convertSingleDtoToEntity(dto, user);
|
|
64
|
-
await this.incrementSchemaVersionIfChanged(dto, entity);
|
|
65
|
-
if (this.emailConfig.isCompanyFeatureEnabled()) {
|
|
66
|
-
entity.companyId = user?.companyId ?? null;
|
|
67
|
-
}
|
|
68
|
-
return entity;
|
|
69
|
-
}
|
|
70
|
-
async getSelectQuery(query, _user, select) {
|
|
71
|
-
if (!select?.length) {
|
|
72
|
-
select = [
|
|
73
|
-
...DEFAULT_SELECT_FIELDS
|
|
74
|
-
];
|
|
75
|
-
if (this.emailConfig.isCompanyFeatureEnabled()) select.push('companyId');
|
|
76
|
-
}
|
|
77
|
-
query.select(select.map((field)=>`${this.entityName}.${field}`));
|
|
78
|
-
return {
|
|
79
|
-
query,
|
|
80
|
-
isRaw: false
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
async getExtraManipulateQuery(query, filterDto, user) {
|
|
84
|
-
const result = await super.getExtraManipulateQuery(query, filterDto, user);
|
|
85
|
-
(0, _utils.applyCompanyFilter)(query, {
|
|
86
|
-
isCompanyFeatureEnabled: this.emailConfig.isCompanyFeatureEnabled(),
|
|
87
|
-
entityAlias: 'emailTemplate'
|
|
88
|
-
}, user);
|
|
89
|
-
query.orderBy(`${this.entityName}.createdAt`, 'DESC');
|
|
90
|
-
return result;
|
|
91
|
-
}
|
|
92
|
-
// ─── Public Query Methods ───────────────────────────────────────────────────
|
|
93
|
-
async findByIdDirect(id) {
|
|
94
|
-
await this.ensureDataSourceRepository();
|
|
95
|
-
return this.repository.findOne({
|
|
96
|
-
where: {
|
|
97
|
-
id
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
async findBySlug(slug, user) {
|
|
102
|
-
await this.ensureDataSourceRepository();
|
|
103
|
-
const where = (0, _utils.buildCompanyWhereCondition)({
|
|
104
|
-
slug,
|
|
105
|
-
isActive: true
|
|
106
|
-
}, this.emailConfig.isCompanyFeatureEnabled(), user);
|
|
107
|
-
return this.repository.findOne({
|
|
108
|
-
where
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
// ─── Private Helpers ────────────────────────────────────────────────────────
|
|
112
|
-
async incrementSchemaVersionIfChanged(dto, entity) {
|
|
113
|
-
const updateDto = dto;
|
|
114
|
-
if (!updateDto.id || !dto.schema) return;
|
|
115
|
-
await this.ensureDataSourceRepository();
|
|
116
|
-
const existing = await this.repository.findOne({
|
|
117
|
-
where: {
|
|
118
|
-
id: updateDto.id
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
if (existing && JSON.stringify(dto.schema) !== JSON.stringify(existing.schema)) {
|
|
122
|
-
entity.schemaVersion = existing.schemaVersion + 1;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
constructor(cacheManager, utilsService, emailConfig, dataSourceProvider){
|
|
126
|
-
super(_config.EMAIL_EVENT_ENTITIES.TEMPLATE, cacheManager, utilsService, EmailTemplateService.name, true, _config.EMAIL_EVENT_MODULE, dataSourceProvider, emailConfig.isCompanyFeatureEnabled() ? _entities.EmailTemplateWithCompany : _entities.EmailTemplate), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "emailConfig", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.emailConfig = emailConfig;
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
EmailTemplateService = _ts_decorate([
|
|
130
|
-
(0, _common.Injectable)({
|
|
131
|
-
scope: _common.Scope.REQUEST
|
|
132
|
-
}),
|
|
133
|
-
_ts_param(0, (0, _common.Inject)('CACHE_INSTANCE')),
|
|
134
|
-
_ts_param(1, (0, _common.Inject)(_modules.UtilsService)),
|
|
135
|
-
_ts_param(2, (0, _common.Inject)(_emailconfigservice.EmailConfigService)),
|
|
136
|
-
_ts_param(3, (0, _common.Inject)(_emaildatasourceprovider.EmailDataSourceProvider)),
|
|
137
|
-
_ts_metadata("design:type", Function),
|
|
138
|
-
_ts_metadata("design:paramtypes", [
|
|
139
|
-
typeof _classes.HybridCache === "undefined" ? Object : _classes.HybridCache,
|
|
140
|
-
typeof _modules.UtilsService === "undefined" ? Object : _modules.UtilsService,
|
|
141
|
-
typeof _emailconfigservice.EmailConfigService === "undefined" ? Object : _emailconfigservice.EmailConfigService,
|
|
142
|
-
typeof _emaildatasourceprovider.EmailDataSourceProvider === "undefined" ? Object : _emaildatasourceprovider.EmailDataSourceProvider
|
|
143
|
-
])
|
|
144
|
-
], EmailTemplateService);
|
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", {
|
|
3
|
-
value: true
|
|
4
|
-
});
|
|
5
|
-
const _testing = require("@nestjs/testing");
|
|
6
|
-
const _modules = require("@flusys/nestjs-shared/modules");
|
|
7
|
-
const _repositorymock = require("@test-utils/mocks/repository.mock");
|
|
8
|
-
const _loggedusermock = require("@test-utils/mocks/logged-user.mock");
|
|
9
|
-
const _entities = require("../entities");
|
|
10
|
-
const _emailconfigservice = require("./email-config.service");
|
|
11
|
-
const _emaildatasourceprovider = require("./email-datasource.provider");
|
|
12
|
-
const _emailtemplateservice = require("./email-template.service");
|
|
13
|
-
function buildTemplate(overrides = {}) {
|
|
14
|
-
return {
|
|
15
|
-
id: 'template-uuid-1',
|
|
16
|
-
name: 'Welcome Email',
|
|
17
|
-
slug: 'welcome-email',
|
|
18
|
-
description: null,
|
|
19
|
-
subject: 'Welcome {{firstName}}!',
|
|
20
|
-
schema: {
|
|
21
|
-
fields: []
|
|
22
|
-
},
|
|
23
|
-
htmlContent: '<h1>Welcome {{firstName}}!</h1>',
|
|
24
|
-
textContent: 'Welcome {{firstName}}!',
|
|
25
|
-
schemaVersion: 1,
|
|
26
|
-
isActive: true,
|
|
27
|
-
isHtml: true,
|
|
28
|
-
createdAt: new Date(),
|
|
29
|
-
updatedAt: new Date(),
|
|
30
|
-
deletedAt: null,
|
|
31
|
-
createdById: null,
|
|
32
|
-
updatedById: null,
|
|
33
|
-
deletedById: null,
|
|
34
|
-
...overrides
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
function buildQuery() {
|
|
38
|
-
return {
|
|
39
|
-
select: jest.fn().mockReturnThis(),
|
|
40
|
-
andWhere: jest.fn().mockReturnThis(),
|
|
41
|
-
orderBy: jest.fn().mockReturnThis()
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
describe('EmailTemplateService', ()=>{
|
|
45
|
-
let service;
|
|
46
|
-
let module;
|
|
47
|
-
let mockRepo;
|
|
48
|
-
let mockEmailConfig;
|
|
49
|
-
beforeEach(async ()=>{
|
|
50
|
-
mockRepo = (0, _repositorymock.createMockRepository)();
|
|
51
|
-
const mockDataSourceProvider = (0, _repositorymock.createMockDataSourceProvider)(mockRepo);
|
|
52
|
-
mockEmailConfig = {
|
|
53
|
-
isCompanyFeatureEnabled: jest.fn().mockReturnValue(false)
|
|
54
|
-
};
|
|
55
|
-
module = await _testing.Test.createTestingModule({
|
|
56
|
-
providers: [
|
|
57
|
-
_emailtemplateservice.EmailTemplateService,
|
|
58
|
-
{
|
|
59
|
-
provide: 'CACHE_INSTANCE',
|
|
60
|
-
useValue: {}
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
provide: _modules.UtilsService,
|
|
64
|
-
useValue: {}
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
provide: _emailconfigservice.EmailConfigService,
|
|
68
|
-
useValue: mockEmailConfig
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
provide: _emaildatasourceprovider.EmailDataSourceProvider,
|
|
72
|
-
useValue: mockDataSourceProvider
|
|
73
|
-
}
|
|
74
|
-
]
|
|
75
|
-
}).compile();
|
|
76
|
-
service = await module.resolve(_emailtemplateservice.EmailTemplateService);
|
|
77
|
-
// convertSingleDtoToEntity accesses `this.repository` directly; normally it is
|
|
78
|
-
// lazily initialized by insert()/update() via ensureDataSourceRepository() first.
|
|
79
|
-
await service.ensureDataSourceRepository();
|
|
80
|
-
});
|
|
81
|
-
describe('entity selection', ()=>{
|
|
82
|
-
it('uses EmailTemplate when the company feature is disabled', async ()=>{
|
|
83
|
-
mockEmailConfig.isCompanyFeatureEnabled.mockReturnValue(false);
|
|
84
|
-
const built = await module.resolve(_emailtemplateservice.EmailTemplateService);
|
|
85
|
-
expect(built['_entity']).toBe(_entities.EmailTemplate);
|
|
86
|
-
});
|
|
87
|
-
it('uses EmailTemplateWithCompany when the company feature is enabled', async ()=>{
|
|
88
|
-
mockEmailConfig.isCompanyFeatureEnabled.mockReturnValue(true);
|
|
89
|
-
const built = await module.resolve(_emailtemplateservice.EmailTemplateService);
|
|
90
|
-
expect(built['_entity']).toBe(_entities.EmailTemplateWithCompany);
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
describe('convertSingleDtoToEntity', ()=>{
|
|
94
|
-
it('creates a new entity (no schema-version bump) for an insert without id', async ()=>{
|
|
95
|
-
mockRepo.create.mockReturnValue({});
|
|
96
|
-
const entity = await service.convertSingleDtoToEntity({
|
|
97
|
-
name: 'Welcome',
|
|
98
|
-
slug: 'welcome',
|
|
99
|
-
subject: 'Hi {{name}}',
|
|
100
|
-
schema: {
|
|
101
|
-
fields: []
|
|
102
|
-
},
|
|
103
|
-
htmlContent: '<p>Hi {{name}}</p>'
|
|
104
|
-
}, (0, _loggedusermock.buildMockUser)());
|
|
105
|
-
expect(entity.schemaVersion).toBeUndefined();
|
|
106
|
-
expect(mockRepo.findOne).not.toHaveBeenCalled();
|
|
107
|
-
});
|
|
108
|
-
it('increments schemaVersion on update when the schema payload changed', async ()=>{
|
|
109
|
-
// convertSingleDtoToEntity (ApiService base) mutates the fetched row in place via
|
|
110
|
-
// Object.assign, and incrementSchemaVersionIfChanged re-fetches by id afterwards —
|
|
111
|
-
// so the mock must return a fresh clone per call, like a real repository would.
|
|
112
|
-
mockRepo.findOne.mockImplementation(async ()=>buildTemplate({
|
|
113
|
-
schema: {
|
|
114
|
-
fields: []
|
|
115
|
-
},
|
|
116
|
-
schemaVersion: 3
|
|
117
|
-
}));
|
|
118
|
-
const entity = await service.convertSingleDtoToEntity({
|
|
119
|
-
id: 'template-uuid-1',
|
|
120
|
-
schema: {
|
|
121
|
-
fields: [
|
|
122
|
-
{
|
|
123
|
-
name: 'x'
|
|
124
|
-
}
|
|
125
|
-
]
|
|
126
|
-
}
|
|
127
|
-
}, (0, _loggedusermock.buildMockUser)());
|
|
128
|
-
expect(entity.schemaVersion).toBe(4);
|
|
129
|
-
});
|
|
130
|
-
it('does not increment schemaVersion on update when the schema payload is unchanged', async ()=>{
|
|
131
|
-
mockRepo.findOne.mockImplementation(async ()=>buildTemplate({
|
|
132
|
-
schema: {
|
|
133
|
-
fields: []
|
|
134
|
-
},
|
|
135
|
-
schemaVersion: 3
|
|
136
|
-
}));
|
|
137
|
-
const entity = await service.convertSingleDtoToEntity({
|
|
138
|
-
id: 'template-uuid-1',
|
|
139
|
-
schema: {
|
|
140
|
-
fields: []
|
|
141
|
-
}
|
|
142
|
-
}, (0, _loggedusermock.buildMockUser)());
|
|
143
|
-
expect(entity.schemaVersion).toBe(3);
|
|
144
|
-
});
|
|
145
|
-
it('does not touch schemaVersion when the update payload has no schema field', async ()=>{
|
|
146
|
-
const existing = buildTemplate({
|
|
147
|
-
schema: {
|
|
148
|
-
fields: []
|
|
149
|
-
},
|
|
150
|
-
schemaVersion: 3
|
|
151
|
-
});
|
|
152
|
-
mockRepo.findOne.mockResolvedValue(existing);
|
|
153
|
-
const entity = await service.convertSingleDtoToEntity({
|
|
154
|
-
id: 'template-uuid-1',
|
|
155
|
-
name: 'Renamed'
|
|
156
|
-
}, (0, _loggedusermock.buildMockUser)());
|
|
157
|
-
expect(entity.schemaVersion).toBe(3);
|
|
158
|
-
// ensureDataSourceRepository/findOne should only be hit once — by the base
|
|
159
|
-
// convertSingleDtoToEntity — not a second time by incrementSchemaVersionIfChanged.
|
|
160
|
-
expect(mockRepo.findOne).toHaveBeenCalledTimes(1);
|
|
161
|
-
});
|
|
162
|
-
it('attaches the current user companyId when the company feature is enabled', async ()=>{
|
|
163
|
-
mockEmailConfig.isCompanyFeatureEnabled.mockReturnValue(true);
|
|
164
|
-
mockRepo.create.mockReturnValue({});
|
|
165
|
-
const entity = await service.convertSingleDtoToEntity({
|
|
166
|
-
name: 'Welcome',
|
|
167
|
-
slug: 'welcome',
|
|
168
|
-
subject: 'Hi',
|
|
169
|
-
schema: {},
|
|
170
|
-
htmlContent: '<p/>'
|
|
171
|
-
}, (0, _loggedusermock.buildMockUser)({
|
|
172
|
-
companyId: 'company-uuid-7'
|
|
173
|
-
}));
|
|
174
|
-
expect(entity.companyId).toBe('company-uuid-7');
|
|
175
|
-
});
|
|
176
|
-
});
|
|
177
|
-
describe('getSelectQuery', ()=>{
|
|
178
|
-
it('selects the default whitelisted fields (without companyId) when company feature is disabled', async ()=>{
|
|
179
|
-
const query = buildQuery();
|
|
180
|
-
await service.getSelectQuery(query, (0, _loggedusermock.buildMockUser)());
|
|
181
|
-
const [selected] = query.select.mock.calls[0];
|
|
182
|
-
expect(selected).toEqual(expect.arrayContaining([
|
|
183
|
-
'emailTemplate.id',
|
|
184
|
-
'emailTemplate.name',
|
|
185
|
-
'emailTemplate.slug',
|
|
186
|
-
'emailTemplate.subject',
|
|
187
|
-
'emailTemplate.htmlContent',
|
|
188
|
-
'emailTemplate.textContent',
|
|
189
|
-
'emailTemplate.schemaVersion',
|
|
190
|
-
'emailTemplate.isActive',
|
|
191
|
-
'emailTemplate.isHtml'
|
|
192
|
-
]));
|
|
193
|
-
expect(selected).not.toContain('emailTemplate.companyId');
|
|
194
|
-
});
|
|
195
|
-
it('appends companyId to the default field list when company feature is enabled', async ()=>{
|
|
196
|
-
mockEmailConfig.isCompanyFeatureEnabled.mockReturnValue(true);
|
|
197
|
-
const query = buildQuery();
|
|
198
|
-
await service.getSelectQuery(query, (0, _loggedusermock.buildMockUser)());
|
|
199
|
-
const [selected] = query.select.mock.calls[0];
|
|
200
|
-
expect(selected).toContain('emailTemplate.companyId');
|
|
201
|
-
});
|
|
202
|
-
});
|
|
203
|
-
describe('getExtraManipulateQuery', ()=>{
|
|
204
|
-
it('applies the company filter and orders by createdAt DESC when company feature is enabled', async ()=>{
|
|
205
|
-
mockEmailConfig.isCompanyFeatureEnabled.mockReturnValue(true);
|
|
206
|
-
const query = buildQuery();
|
|
207
|
-
await service.getExtraManipulateQuery(query, {}, (0, _loggedusermock.buildMockUser)({
|
|
208
|
-
companyId: 'c-1'
|
|
209
|
-
}));
|
|
210
|
-
expect(query.andWhere).toHaveBeenCalledWith('emailTemplate.companyId = :companyId', {
|
|
211
|
-
companyId: 'c-1'
|
|
212
|
-
});
|
|
213
|
-
expect(query.orderBy).toHaveBeenCalledWith('emailTemplate.createdAt', 'DESC');
|
|
214
|
-
});
|
|
215
|
-
});
|
|
216
|
-
describe('findByIdDirect', ()=>{
|
|
217
|
-
it('returns the matching template', async ()=>{
|
|
218
|
-
const template = buildTemplate();
|
|
219
|
-
mockRepo.findOne.mockResolvedValue(template);
|
|
220
|
-
const result = await service.findByIdDirect('template-uuid-1');
|
|
221
|
-
expect(result).toBe(template);
|
|
222
|
-
expect(mockRepo.findOne).toHaveBeenCalledWith({
|
|
223
|
-
where: {
|
|
224
|
-
id: 'template-uuid-1'
|
|
225
|
-
}
|
|
226
|
-
});
|
|
227
|
-
});
|
|
228
|
-
});
|
|
229
|
-
describe('findBySlug', ()=>{
|
|
230
|
-
it('finds an active template by slug', async ()=>{
|
|
231
|
-
const template = buildTemplate();
|
|
232
|
-
mockRepo.findOne.mockResolvedValue(template);
|
|
233
|
-
const result = await service.findBySlug('welcome-email', (0, _loggedusermock.buildMockUser)());
|
|
234
|
-
expect(result).toBe(template);
|
|
235
|
-
expect(mockRepo.findOne).toHaveBeenCalledWith({
|
|
236
|
-
where: {
|
|
237
|
-
slug: 'welcome-email',
|
|
238
|
-
isActive: true
|
|
239
|
-
}
|
|
240
|
-
});
|
|
241
|
-
});
|
|
242
|
-
it('scopes the slug lookup to the current company when the company feature is enabled', async ()=>{
|
|
243
|
-
mockEmailConfig.isCompanyFeatureEnabled.mockReturnValue(true);
|
|
244
|
-
mockRepo.findOne.mockResolvedValue(null);
|
|
245
|
-
await service.findBySlug('welcome-email', (0, _loggedusermock.buildMockUser)({
|
|
246
|
-
companyId: 'company-uuid-3'
|
|
247
|
-
}));
|
|
248
|
-
expect(mockRepo.findOne).toHaveBeenCalledWith({
|
|
249
|
-
where: {
|
|
250
|
-
slug: 'welcome-email',
|
|
251
|
-
isActive: true,
|
|
252
|
-
companyId: 'company-uuid-3'
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
});
|
|
256
|
-
it('returns null when no template matches the slug', async ()=>{
|
|
257
|
-
mockRepo.findOne.mockResolvedValue(null);
|
|
258
|
-
await expect(service.findBySlug('missing-slug', (0, _loggedusermock.buildMockUser)())).resolves.toBeNull();
|
|
259
|
-
});
|
|
260
|
-
});
|
|
261
|
-
});
|
package/cjs/services/index.js
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", {
|
|
3
|
-
value: true
|
|
4
|
-
});
|
|
5
|
-
_export_star(require("./email-config.service"), exports);
|
|
6
|
-
_export_star(require("./email-datasource.provider"), exports);
|
|
7
|
-
_export_star(require("./email-provider-config.service"), exports);
|
|
8
|
-
_export_star(require("./email-send.service"), exports);
|
|
9
|
-
_export_star(require("./email-template.service"), exports);
|
|
10
|
-
function _export_star(from, to) {
|
|
11
|
-
Object.keys(from).forEach(function(k) {
|
|
12
|
-
if (k !== "default" && !Object.prototype.hasOwnProperty.call(to, k)) {
|
|
13
|
-
Object.defineProperty(to, k, {
|
|
14
|
-
enumerable: true,
|
|
15
|
-
get: function() {
|
|
16
|
-
return from[k];
|
|
17
|
-
}
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
});
|
|
21
|
-
return from;
|
|
22
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", {
|
|
3
|
-
value: true
|
|
4
|
-
});
|
|
5
|
-
function _export(target, all) {
|
|
6
|
-
for(var name in all)Object.defineProperty(target, name, {
|
|
7
|
-
enumerable: true,
|
|
8
|
-
get: Object.getOwnPropertyDescriptor(all, name).get
|
|
9
|
-
});
|
|
10
|
-
}
|
|
11
|
-
_export(exports, {
|
|
12
|
-
get getOtpEmailFormat () {
|
|
13
|
-
return getOtpEmailFormat;
|
|
14
|
-
},
|
|
15
|
-
get getResetPasswordEmailFormat () {
|
|
16
|
-
return getResetPasswordEmailFormat;
|
|
17
|
-
}
|
|
18
|
-
});
|
|
19
|
-
// ─── Shared Styles ───────────────────────────────────────────────────────────
|
|
20
|
-
const BASE_STYLES = `
|
|
21
|
-
body { font-family: Arial, sans-serif; background-color: #f4f4f7; margin: 0; padding: 0; }
|
|
22
|
-
.email-container { max-width: 500px; margin: 40px auto; background-color: #ffffff; padding: 30px; border-radius: 8px; box-shadow: 0 2px 6px rgba(0,0,0,0.1); text-align: center; }
|
|
23
|
-
h1 { color: #333; }
|
|
24
|
-
p { font-size: 16px; color: #555; }
|
|
25
|
-
.action-box { display: inline-block; margin: 20px 0; padding: 14px 28px; background-color: #007BFF; color: #ffffff; border-radius: 8px; font-weight: bold; text-decoration: none; }
|
|
26
|
-
`;
|
|
27
|
-
function wrapEmailTemplate(title, extraStyles, content) {
|
|
28
|
-
return `<!DOCTYPE html>
|
|
29
|
-
<html lang="en">
|
|
30
|
-
<head>
|
|
31
|
-
<meta charset="UTF-8" />
|
|
32
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
|
33
|
-
<title>${title}</title>
|
|
34
|
-
<style>${BASE_STYLES}${extraStyles}</style>
|
|
35
|
-
</head>
|
|
36
|
-
<body>
|
|
37
|
-
<div class="email-container">${content}</div>
|
|
38
|
-
</body>
|
|
39
|
-
</html>`;
|
|
40
|
-
}
|
|
41
|
-
function getGreeting(userName) {
|
|
42
|
-
return `<p>Hi ${userName || 'Sir/Madam'},</p>`;
|
|
43
|
-
}
|
|
44
|
-
function getOtpEmailFormat(otp, userName) {
|
|
45
|
-
const extraStyles = `.action-box { font-size: 24px; letter-spacing: 6px; }`;
|
|
46
|
-
const content = `
|
|
47
|
-
${getGreeting(userName)}
|
|
48
|
-
<p>Use the code below to verify your identity:</p>
|
|
49
|
-
<div class="action-box">${otp}</div>
|
|
50
|
-
<p>This OTP is valid for a limited time. Do not share it with anyone.</p>
|
|
51
|
-
<p>If you didn't request this, please ignore this email.</p>
|
|
52
|
-
`;
|
|
53
|
-
return wrapEmailTemplate('Your OTP Code', extraStyles, content);
|
|
54
|
-
}
|
|
55
|
-
function getResetPasswordEmailFormat(resetLink, userName) {
|
|
56
|
-
const extraStyles = `.action-box { font-size: 16px; }`;
|
|
57
|
-
const content = `
|
|
58
|
-
${getGreeting(userName)}
|
|
59
|
-
<p>We received a request to reset your password. Click the button below to reset it:</p>
|
|
60
|
-
<a href="${resetLink}" class="action-box" target="_blank">Reset Password</a>
|
|
61
|
-
<p>If you didn't request a password reset, you can safely ignore this email.</p>
|
|
62
|
-
`;
|
|
63
|
-
return wrapEmailTemplate('Reset Your Password', extraStyles, content);
|
|
64
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", {
|
|
3
|
-
value: true
|
|
4
|
-
});
|
|
5
|
-
const _emailtemplatesutil = require("./email-templates.util");
|
|
6
|
-
describe('email-templates.util', ()=>{
|
|
7
|
-
describe('getOtpEmailFormat', ()=>{
|
|
8
|
-
it('renders the OTP inside the generated HTML document', ()=>{
|
|
9
|
-
const html = (0, _emailtemplatesutil.getOtpEmailFormat)(123456, 'Jane');
|
|
10
|
-
expect(html).toContain('<!DOCTYPE html>');
|
|
11
|
-
expect(html).toContain('123456');
|
|
12
|
-
expect(html).toContain('Hi Jane,');
|
|
13
|
-
expect(html).toContain('Your OTP Code');
|
|
14
|
-
});
|
|
15
|
-
it('falls back to a generic greeting when userName is not provided', ()=>{
|
|
16
|
-
const html = (0, _emailtemplatesutil.getOtpEmailFormat)(654321);
|
|
17
|
-
expect(html).toContain('Hi Sir/Madam,');
|
|
18
|
-
});
|
|
19
|
-
it('falls back to a generic greeting when userName is null', ()=>{
|
|
20
|
-
const html = (0, _emailtemplatesutil.getOtpEmailFormat)(111111, null);
|
|
21
|
-
expect(html).toContain('Hi Sir/Madam,');
|
|
22
|
-
});
|
|
23
|
-
it('does not escape HTML special characters in userName (XSS caveat)', ()=>{
|
|
24
|
-
const html = (0, _emailtemplatesutil.getOtpEmailFormat)(999999, '<script>alert(1)</script>');
|
|
25
|
-
// Documents CURRENT behavior: userName is interpolated verbatim, unescaped.
|
|
26
|
-
expect(html).toContain('<script>alert(1)</script>');
|
|
27
|
-
expect(html).not.toContain('<script>');
|
|
28
|
-
});
|
|
29
|
-
it('includes styling for the OTP action box', ()=>{
|
|
30
|
-
const html = (0, _emailtemplatesutil.getOtpEmailFormat)(1, 'A');
|
|
31
|
-
expect(html).toContain('letter-spacing: 6px');
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
describe('getResetPasswordEmailFormat', ()=>{
|
|
35
|
-
it('renders the reset link as the href of the action button', ()=>{
|
|
36
|
-
const html = (0, _emailtemplatesutil.getResetPasswordEmailFormat)('https://app.example.com/reset?token=abc', 'Bob');
|
|
37
|
-
expect(html).toContain('href="https://app.example.com/reset?token=abc"');
|
|
38
|
-
expect(html).toContain('Hi Bob,');
|
|
39
|
-
expect(html).toContain('Reset Your Password');
|
|
40
|
-
});
|
|
41
|
-
it('falls back to a generic greeting when userName is omitted', ()=>{
|
|
42
|
-
const html = (0, _emailtemplatesutil.getResetPasswordEmailFormat)('https://app.example.com/reset?token=abc');
|
|
43
|
-
expect(html).toContain('Hi Sir/Madam,');
|
|
44
|
-
});
|
|
45
|
-
it('does not escape or validate the reset link (XSS/HTML-injection caveat)', ()=>{
|
|
46
|
-
const maliciousLink = '"><script>alert(1)</script>';
|
|
47
|
-
const html = (0, _emailtemplatesutil.getResetPasswordEmailFormat)(maliciousLink, 'Bob');
|
|
48
|
-
// Documents CURRENT behavior: resetLink is interpolated verbatim, unescaped,
|
|
49
|
-
// allowing HTML attribute breakout if the value is attacker-influenced.
|
|
50
|
-
expect(html).toContain(`href="${maliciousLink}"`);
|
|
51
|
-
});
|
|
52
|
-
it('does not escape HTML special characters in userName', ()=>{
|
|
53
|
-
const html = (0, _emailtemplatesutil.getResetPasswordEmailFormat)('https://x.test/reset', '<img src=x onerror=alert(1)>');
|
|
54
|
-
expect(html).toContain('<img src=x onerror=alert(1)>');
|
|
55
|
-
});
|
|
56
|
-
it('produces a single-line action-box style for the reset button', ()=>{
|
|
57
|
-
const html = (0, _emailtemplatesutil.getResetPasswordEmailFormat)('https://x.test/reset', 'A');
|
|
58
|
-
expect(html).toContain('font-size: 16px');
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
});
|
package/cjs/utils/index.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", {
|
|
3
|
-
value: true
|
|
4
|
-
});
|
|
5
|
-
_export_star(require("./email-templates.util"), exports);
|
|
6
|
-
function _export_star(from, to) {
|
|
7
|
-
Object.keys(from).forEach(function(k) {
|
|
8
|
-
if (k !== "default" && !Object.prototype.hasOwnProperty.call(to, k)) {
|
|
9
|
-
Object.defineProperty(to, k, {
|
|
10
|
-
enumerable: true,
|
|
11
|
-
get: function() {
|
|
12
|
-
return from[k];
|
|
13
|
-
}
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
});
|
|
17
|
-
return from;
|
|
18
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Domain event actions published by the email module, alongside the CRUD
|
|
3
|
-
* lifecycle actions every ApiService emits.
|
|
4
|
-
*/ export const EMAIL_EVENT_ACTIONS = {
|
|
5
|
-
SENT: 'sent',
|
|
6
|
-
FAILED: 'failed'
|
|
7
|
-
};
|
|
8
|
-
/** Every entity the email module publishes under, ApiService backed or not */ export const EMAIL_EVENT_ENTITIES = {
|
|
9
|
-
MESSAGE: 'email_message',
|
|
10
|
-
CONFIG: 'emailConfig',
|
|
11
|
-
TEMPLATE: 'emailTemplate'
|
|
12
|
-
};
|
|
13
|
-
export const EMAIL_EVENT_MODULE = 'email';
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
// ==================== EMAIL MODULE MESSAGE KEYS ====================
|
|
2
|
-
export const EMAIL_TEMPLATE_MESSAGES = {
|
|
3
|
-
GET_SUCCESS: 'email.template.get.success',
|
|
4
|
-
NOT_FOUND: 'email.template.not.found'
|
|
5
|
-
};
|
|
6
|
-
export const EMAIL_CONFIG_MESSAGES = {
|
|
7
|
-
DEFAULT_DELETE_FORBIDDEN: 'email.config.default.delete.forbidden'
|
|
8
|
-
};
|
|
9
|
-
export const EMAIL_SEND_MESSAGES = {
|
|
10
|
-
SUCCESS: 'email.send.success',
|
|
11
|
-
FAILED: 'email.send.failed',
|
|
12
|
-
CONFIG_NOT_FOUND: 'email.send.config.not.found',
|
|
13
|
-
CONFIG_INACTIVE: 'email.send.config.inactive',
|
|
14
|
-
CONFIG_DEFAULT_NOT_FOUND: 'email.send.config.default.not.found',
|
|
15
|
-
TEMPLATE_NOT_FOUND: 'email.send.template.not.found',
|
|
16
|
-
TEMPLATE_INACTIVE: 'email.send.template.inactive',
|
|
17
|
-
TEMPLATE_ID_OR_SLUG_REQUIRED: 'email.send.template.id.or.slug.required'
|
|
18
|
-
};
|