@adaptivestone/framework 4.11.4 → 5.0.0-alpha.10
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/.eslintrc.cjs +41 -0
- package/CHANGELOG.md +52 -0
- package/Cli.js +5 -5
- package/cluster.js +5 -3
- package/commands/CreateUser.js +5 -3
- package/commands/Documentation.js +3 -3
- package/commands/DropIndex.js +2 -2
- package/commands/Generate.js +2 -2
- package/commands/GetOpenApiJson.js +3 -3
- package/commands/SyncIndexes.js +6 -4
- package/commands/migration/Create.js +4 -7
- package/commands/migration/Migrate.js +8 -4
- package/config/auth.js +3 -2
- package/config/http.js +1 -1
- package/config/i18n.js +1 -1
- package/config/ipDetector.js +14 -0
- package/config/log.js +1 -1
- package/config/mail.js +2 -2
- package/config/mongo.js +1 -1
- package/config/rateLimiter.js +1 -1
- package/config/redis.js +1 -1
- package/config/validate.js +1 -1
- package/controllers/Auth.js +5 -5
- package/controllers/Home.js +3 -3
- package/controllers/index.js +6 -4
- package/folderConfig.js +2 -3
- package/helpers/files.js +11 -7
- package/helpers/logger.js +2 -4
- package/helpers/redis/clearNamespace.js +1 -1
- package/helpers/yup.js +4 -5
- package/index.js +2 -2
- package/jsconfig.json +9 -0
- package/models/Migration.js +2 -2
- package/models/Sequence.js +2 -2
- package/models/User.js +5 -6
- package/modules/AbstractCommand.js +4 -3
- package/modules/AbstractConnector.js +2 -2
- package/modules/AbstractController.js +12 -7
- package/modules/AbstractModel.js +8 -5
- package/modules/Base.d.ts +4 -3
- package/modules/Base.js +2 -2
- package/modules/BaseCli.js +6 -4
- package/modules/Modules.test.js +2 -2
- package/package.json +16 -16
- package/server.d.ts +7 -5
- package/server.js +24 -17
- package/services/cache/Cache.d.ts +2 -2
- package/services/cache/Cache.js +6 -4
- package/services/documentation/DocumentationGenerator.js +3 -3
- package/services/http/HttpServer.js +16 -24
- package/services/http/middleware/AbstractMiddleware.js +3 -3
- package/services/http/middleware/Auth.js +2 -2
- package/services/http/middleware/Auth.test.js +1 -1
- package/services/http/middleware/Cors.js +2 -2
- package/services/http/middleware/Cors.test.js +1 -1
- package/services/http/middleware/GetUserByToken.js +3 -3
- package/services/http/middleware/GetUserByToken.test.js +1 -1
- package/services/http/middleware/I18n.js +5 -6
- package/services/http/middleware/I18n.test.js +1 -1
- package/services/http/middleware/IpDetector.js +59 -0
- package/services/http/middleware/IpDetector.test.js +143 -0
- package/services/http/middleware/Pagination.js +3 -3
- package/services/http/middleware/PrepareAppInfo.js +2 -2
- package/services/http/middleware/PrepareAppInfo.test.js +1 -1
- package/services/http/middleware/RateLimiter.js +14 -9
- package/services/http/middleware/RateLimiter.test.js +5 -3
- package/services/http/middleware/RequestLogger.js +2 -2
- package/services/http/middleware/RequestParser.js +7 -5
- package/services/http/middleware/RequestParser.test.js +17 -10
- package/services/http/middleware/Role.js +2 -2
- package/services/http/middleware/Role.test.js +1 -1
- package/services/messaging/email/index.js +28 -29
- package/services/messaging/index.js +2 -4
- package/services/validate/ValidateService.js +5 -5
- package/services/validate/ValidateService.test.js +4 -4
- package/services/validate/drivers/AbstractValidator.js +2 -2
- package/services/validate/drivers/CustomValidator.js +3 -4
- package/services/validate/drivers/YupValidator.js +3 -3
- package/tests/globalSetupVitest.js +1 -1
- package/tests/setup.js +10 -9
- package/tests/setupVitest.js +6 -7
- package/types/TFoldersConfig.d.ts +0 -2
- package/services/http/middleware/StaticFiles.js +0 -60
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { BlockList } from 'node:net';
|
|
2
|
+
|
|
3
|
+
import AbstractMiddleware from './AbstractMiddleware.js';
|
|
4
|
+
|
|
5
|
+
class IpDetector extends AbstractMiddleware {
|
|
6
|
+
static get description() {
|
|
7
|
+
return 'Detect real user IP address. Support proxy and load balancer';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
constructor(app, params) {
|
|
11
|
+
super(app, params);
|
|
12
|
+
const { trustedProxy } = this.app.getConfig('ipDetector');
|
|
13
|
+
|
|
14
|
+
this.blockList = new BlockList();
|
|
15
|
+
|
|
16
|
+
for (const subnet of trustedProxy) {
|
|
17
|
+
const addressType = subnet.includes(':') ? 'ipv6' : 'ipv4';
|
|
18
|
+
if (subnet.includes('/')) {
|
|
19
|
+
// CIDR
|
|
20
|
+
const [realSubnet, prefixLength] = subnet.split('/');
|
|
21
|
+
this.blockList.addSubnet(
|
|
22
|
+
realSubnet,
|
|
23
|
+
parseInt(prefixLength, 10),
|
|
24
|
+
addressType,
|
|
25
|
+
);
|
|
26
|
+
} else if (subnet.includes('-')) {
|
|
27
|
+
// RANGE
|
|
28
|
+
const [start, end] = subnet.split('-');
|
|
29
|
+
this.blockList.addRange(start, end, addressType);
|
|
30
|
+
} else {
|
|
31
|
+
// just an address
|
|
32
|
+
this.blockList.addAddress(subnet, addressType);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async middleware(req, res, next) {
|
|
38
|
+
const { headers } = this.app.getConfig('ipDetector');
|
|
39
|
+
const initialIp = req.socket.remoteAddress;
|
|
40
|
+
req.appInfo.ip = initialIp;
|
|
41
|
+
const addressType = initialIp.includes(':') ? 'ipv6' : 'ipv4';
|
|
42
|
+
|
|
43
|
+
if (this.blockList.check(initialIp, addressType)) {
|
|
44
|
+
// we can trust this source
|
|
45
|
+
for (const header of headers) {
|
|
46
|
+
// in a range
|
|
47
|
+
const ipHeader = req.headers[header.toLowerCase()];
|
|
48
|
+
if (ipHeader) {
|
|
49
|
+
const [firstIp] = ipHeader.split(',').map((ip) => ip.trim());
|
|
50
|
+
req.appInfo.ip = firstIp;
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
next();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export default IpDetector;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import IpDetector from './IpDetector.js';
|
|
3
|
+
|
|
4
|
+
const testVectors = [
|
|
5
|
+
// IPv4 CIDR blocks
|
|
6
|
+
{
|
|
7
|
+
cidr: '192.168.0.0/16',
|
|
8
|
+
tests: [
|
|
9
|
+
{ ip: '192.168.1.1', matches: true },
|
|
10
|
+
{ ip: '192.169.1.1', matches: false },
|
|
11
|
+
],
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
cidr: '10.0.0.0/8',
|
|
15
|
+
tests: [
|
|
16
|
+
{ ip: '10.0.0.1', matches: true },
|
|
17
|
+
{ ip: '11.0.0.1', matches: false },
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
cidr: '172.16.0.0/12',
|
|
22
|
+
tests: [
|
|
23
|
+
{ ip: '172.16.0.1', matches: true },
|
|
24
|
+
{ ip: '172.32.0.1', matches: false },
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
// // IPv6 CIDR blocks
|
|
29
|
+
{
|
|
30
|
+
cidr: '2001:db8::/32',
|
|
31
|
+
tests: [
|
|
32
|
+
{ ip: '2001:db8::1', matches: true },
|
|
33
|
+
{ ip: '2001:db9::1', matches: false },
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
cidr: 'fe80::/10',
|
|
38
|
+
tests: [
|
|
39
|
+
{ ip: 'fe80::1', matches: true },
|
|
40
|
+
{ ip: 'fec0::1', matches: false },
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
cidr: '::ffff:0:0/96',
|
|
45
|
+
tests: [
|
|
46
|
+
{ ip: '::ffff:192.0.2.1', matches: true },
|
|
47
|
+
{ ip: '2001:db8::1', matches: false },
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
// // Specific IPv4 addresses
|
|
52
|
+
{
|
|
53
|
+
cidr: '203.0.113.1/32',
|
|
54
|
+
tests: [
|
|
55
|
+
{ ip: '203.0.113.1', matches: true },
|
|
56
|
+
{ ip: '203.0.113.2', matches: false },
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
// // Specific IPv6 addresses
|
|
61
|
+
{
|
|
62
|
+
cidr: '2001:db8:85a3::8a2e:370:7334/128',
|
|
63
|
+
tests: [
|
|
64
|
+
{ ip: '2001:db8:85a3::8a2e:370:7334', matches: true },
|
|
65
|
+
{ ip: '2001:db8:85a3::8a2e:370:7335', matches: false },
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
// // Mixed scenarios
|
|
70
|
+
{
|
|
71
|
+
cidr: '::ffff:192.0.2.0/120',
|
|
72
|
+
tests: [
|
|
73
|
+
{ ip: '::ffff:192.0.2.1', matches: true },
|
|
74
|
+
{ ip: '192.0.2.1', matches: true }, // IPv4-mapped addresses should match their IPv4 equivalents
|
|
75
|
+
{ ip: '::ffff:192.0.3.1', matches: false },
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
// // Edge cases
|
|
80
|
+
{
|
|
81
|
+
cidr: '0.0.0.0/0',
|
|
82
|
+
tests: [
|
|
83
|
+
{ ip: '0.0.0.0', matches: true },
|
|
84
|
+
{ ip: '255.255.255.255', matches: true },
|
|
85
|
+
{ ip: '2001:db8::1', matches: false }, // Matches any IPv4 but not IPv6
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
cidr: '::/0',
|
|
90
|
+
tests: [
|
|
91
|
+
{ ip: '::1', matches: true },
|
|
92
|
+
{ ip: 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', matches: true },
|
|
93
|
+
{ ip: '192.168.1.1', matches: true },
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
cidr: '8.8.8.8-8.8.8.10', // our feature range
|
|
98
|
+
tests: [
|
|
99
|
+
{ ip: '8.8.8.7', matches: false },
|
|
100
|
+
{ ip: '8.8.8.8', matches: true },
|
|
101
|
+
{ ip: '8.8.8.9', matches: true },
|
|
102
|
+
{ ip: '8.8.8.10', matches: true },
|
|
103
|
+
{ ip: '8.8.8.11', matches: false },
|
|
104
|
+
],
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
cidr: '1.1.1.1', // one ip
|
|
108
|
+
tests: [
|
|
109
|
+
{ ip: '8.8.8.7', matches: false },
|
|
110
|
+
{ ip: '1.1.1.1', matches: true },
|
|
111
|
+
],
|
|
112
|
+
},
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
describe('ipDetector methods', () => {
|
|
116
|
+
it('have description fields', async () => {
|
|
117
|
+
expect.assertions(1);
|
|
118
|
+
const middleware = new IpDetector(global.server.app);
|
|
119
|
+
expect(middleware.constructor.description).toBeDefined();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('middleware that works', async () => {
|
|
123
|
+
expect.hasAssertions();
|
|
124
|
+
const nextFunction = () => {};
|
|
125
|
+
for (const vector of testVectors) {
|
|
126
|
+
global.server.app.updateConfig('ipDetector', {
|
|
127
|
+
trustedProxy: [vector.cidr],
|
|
128
|
+
});
|
|
129
|
+
const middleware = new IpDetector(global.server.app);
|
|
130
|
+
for (const test of vector.tests) {
|
|
131
|
+
const req = {
|
|
132
|
+
appInfo: {},
|
|
133
|
+
headers: { 'x-forwarded-for': 'notAnIP' },
|
|
134
|
+
socket: { remoteAddress: test.ip },
|
|
135
|
+
};
|
|
136
|
+
// eslint-disable-next-line no-await-in-loop
|
|
137
|
+
await middleware.middleware(req, {}, nextFunction);
|
|
138
|
+
const result = req.appInfo.ip === 'notAnIP';
|
|
139
|
+
expect(result).toBe(test.matches);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import yup from 'yup';
|
|
2
|
+
import AbstractMiddleware from './AbstractMiddleware.js';
|
|
3
3
|
/**
|
|
4
4
|
* Middleware for reusing pagination
|
|
5
5
|
*/
|
|
@@ -53,4 +53,4 @@ class Pagination extends AbstractMiddleware {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
export default Pagination;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import AbstractMiddleware from './AbstractMiddleware.js';
|
|
2
2
|
|
|
3
3
|
class PrepareAppInfo extends AbstractMiddleware {
|
|
4
4
|
static get description() {
|
|
@@ -15,4 +15,4 @@ class PrepareAppInfo extends AbstractMiddleware {
|
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
export default PrepareAppInfo;
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
import {
|
|
2
2
|
RateLimiterMemory,
|
|
3
3
|
RateLimiterRedis,
|
|
4
4
|
RateLimiterMongo,
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const AbstractMiddleware = require('./AbstractMiddleware');
|
|
5
|
+
} from 'rate-limiter-flexible';
|
|
6
|
+
import merge from 'deepmerge';
|
|
7
|
+
import redis from 'redis';
|
|
8
|
+
import mongoose from 'mongoose';
|
|
9
|
+
import AbstractMiddleware from './AbstractMiddleware.js';
|
|
11
10
|
|
|
12
11
|
class RateLimiter extends AbstractMiddleware {
|
|
13
12
|
static get description() {
|
|
@@ -79,7 +78,13 @@ class RateLimiter extends AbstractMiddleware {
|
|
|
79
78
|
|
|
80
79
|
const key = [];
|
|
81
80
|
if (ip) {
|
|
82
|
-
|
|
81
|
+
if (!req.appInfo.ip) {
|
|
82
|
+
this.logger.error(
|
|
83
|
+
`RateLimiter: Can't get remote address from request. Please check that you used IpDetecor middleware before RateLimiter`,
|
|
84
|
+
);
|
|
85
|
+
} else {
|
|
86
|
+
key.push(req.appInfo.ip);
|
|
87
|
+
}
|
|
83
88
|
}
|
|
84
89
|
if (route) {
|
|
85
90
|
key.push(req.originalUrl);
|
|
@@ -126,4 +131,4 @@ class RateLimiter extends AbstractMiddleware {
|
|
|
126
131
|
}
|
|
127
132
|
}
|
|
128
133
|
|
|
129
|
-
|
|
134
|
+
export default RateLimiter;
|
|
@@ -2,7 +2,7 @@ import { setTimeout } from 'node:timers/promises';
|
|
|
2
2
|
import crypto from 'node:crypto';
|
|
3
3
|
import { beforeAll, afterAll, describe, it, expect } from 'vitest';
|
|
4
4
|
|
|
5
|
-
import RateLimiter from './RateLimiter';
|
|
5
|
+
import RateLimiter from './RateLimiter.js';
|
|
6
6
|
|
|
7
7
|
let mongoRateLimiter;
|
|
8
8
|
|
|
@@ -58,8 +58,8 @@ describe('rate limiter methods', () => {
|
|
|
58
58
|
});
|
|
59
59
|
|
|
60
60
|
const res = await redisRateLimiter.gerenateConsumeKey({
|
|
61
|
-
ip: '192.168.0.0',
|
|
62
61
|
appInfo: {
|
|
62
|
+
ip: '192.168.0.0',
|
|
63
63
|
user: {
|
|
64
64
|
id: 'someId',
|
|
65
65
|
},
|
|
@@ -80,7 +80,9 @@ describe('rate limiter methods', () => {
|
|
|
80
80
|
});
|
|
81
81
|
|
|
82
82
|
const res = await redisRateLimiter.gerenateConsumeKey({
|
|
83
|
-
|
|
83
|
+
appInfo: {
|
|
84
|
+
ip: '192.168.0.0',
|
|
85
|
+
},
|
|
84
86
|
body: {
|
|
85
87
|
email: 'foo@example.com',
|
|
86
88
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import AbstractMiddleware from './AbstractMiddleware.js';
|
|
2
2
|
|
|
3
3
|
class RequestLogger extends AbstractMiddleware {
|
|
4
4
|
static get description() {
|
|
@@ -19,4 +19,4 @@ class RequestLogger extends AbstractMiddleware {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
export default RequestLogger;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const AbstractMiddleware = require('./AbstractMiddleware');
|
|
1
|
+
import formidable from 'formidable';
|
|
2
|
+
import AbstractMiddleware from './AbstractMiddleware.js';
|
|
4
3
|
|
|
5
4
|
class RequestParser extends AbstractMiddleware {
|
|
6
5
|
static get description() {
|
|
@@ -19,7 +18,10 @@ class RequestParser extends AbstractMiddleware {
|
|
|
19
18
|
[fields, files] = await form.parse(req);
|
|
20
19
|
} catch (err) {
|
|
21
20
|
this.logger.error(`Parsing failed ${err}`);
|
|
22
|
-
return
|
|
21
|
+
return res.status(400).json({
|
|
22
|
+
message: `Error to parse your request. You provided invalid content type or content-length. Please check your request headers and content type.`,
|
|
23
|
+
});
|
|
24
|
+
// return next(err);
|
|
23
25
|
}
|
|
24
26
|
this.logger.verbose(
|
|
25
27
|
`Parsing multipart/formdata request DONE ${Date.now() - time}ms`,
|
|
@@ -35,4 +37,4 @@ class RequestParser extends AbstractMiddleware {
|
|
|
35
37
|
}
|
|
36
38
|
}
|
|
37
39
|
|
|
38
|
-
|
|
40
|
+
export default RequestParser;
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { createServer } from 'node:http';
|
|
2
2
|
import { describe, it, expect } from 'vitest';
|
|
3
|
+
import { PersistentFile } from 'formidable';
|
|
3
4
|
|
|
4
|
-
import RequestParser from './RequestParser';
|
|
5
|
-
|
|
6
|
-
// TODO change on ESM
|
|
7
|
-
const formidable = require('formidable');
|
|
5
|
+
import RequestParser from './RequestParser.js';
|
|
8
6
|
|
|
9
7
|
describe('reqest parser limiter methods', () => {
|
|
10
8
|
it('have description fields', async () => {
|
|
@@ -26,7 +24,7 @@ describe('reqest parser limiter methods', () => {
|
|
|
26
24
|
expect(req.body.title).toBeDefined();
|
|
27
25
|
expect(req.body.multipleFiles).toBeDefined();
|
|
28
26
|
expect(
|
|
29
|
-
req.body.multipleFiles[0] instanceof
|
|
27
|
+
req.body.multipleFiles[0] instanceof PersistentFile,
|
|
30
28
|
).toBeTruthy();
|
|
31
29
|
|
|
32
30
|
res.writeHead(200);
|
|
@@ -81,12 +79,21 @@ d\r
|
|
|
81
79
|
const server = createServer(async (req, res) => {
|
|
82
80
|
req.appInfo = {};
|
|
83
81
|
const middleware = new RequestParser(global.server.app);
|
|
84
|
-
|
|
85
|
-
expect(err).toBeDefined();
|
|
82
|
+
let status;
|
|
86
83
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
84
|
+
const resp = {
|
|
85
|
+
status: (code) => {
|
|
86
|
+
status = code;
|
|
87
|
+
return resp;
|
|
88
|
+
},
|
|
89
|
+
json: () => resp,
|
|
90
|
+
};
|
|
91
|
+
await middleware.middleware(req, resp, () => {});
|
|
92
|
+
expect(status).toBe(400);
|
|
93
|
+
// expect(err).toBeDefined();
|
|
94
|
+
|
|
95
|
+
res.writeHead(200);
|
|
96
|
+
res.end('ok');
|
|
90
97
|
});
|
|
91
98
|
server.listen(null, async () => {
|
|
92
99
|
const chosenPort = server.address().port;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import AbstractMiddleware from './AbstractMiddleware.js';
|
|
2
2
|
|
|
3
3
|
class RoleMiddleware extends AbstractMiddleware {
|
|
4
4
|
static get description() {
|
|
@@ -26,4 +26,4 @@ class RoleMiddleware extends AbstractMiddleware {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
export default RoleMiddleware;
|
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import * as url from 'node:url';
|
|
4
|
+
import { promisify } from 'node:util';
|
|
5
|
+
import nodemailer from 'nodemailer';
|
|
6
|
+
import sendMail from 'nodemailer-sendmail-transport';
|
|
7
|
+
import stub from 'nodemailer-stub-transport';
|
|
8
|
+
import pug from 'pug';
|
|
9
|
+
import juice from 'juice';
|
|
10
|
+
import { convert } from 'html-to-text';
|
|
11
|
+
import Base from '../../../modules/Base.js';
|
|
10
12
|
|
|
11
13
|
const mailTransports = {
|
|
12
14
|
sendMail,
|
|
13
15
|
stub,
|
|
14
16
|
smtp: (data) => data,
|
|
15
17
|
};
|
|
16
|
-
const Base = require('../../../modules/Base');
|
|
17
18
|
|
|
18
19
|
class Mail extends Base {
|
|
19
20
|
/**
|
|
@@ -25,6 +26,7 @@ class Mail extends Base {
|
|
|
25
26
|
*/
|
|
26
27
|
constructor(app, template, templateData = {}, i18n = null) {
|
|
27
28
|
super(app);
|
|
29
|
+
const dirname = url.fileURLToPath(new URL('.', import.meta.url));
|
|
28
30
|
if (!path.isAbsolute(template)) {
|
|
29
31
|
if (
|
|
30
32
|
fs.existsSync(
|
|
@@ -35,11 +37,16 @@ class Mail extends Base {
|
|
|
35
37
|
template,
|
|
36
38
|
)}`;
|
|
37
39
|
} else if (
|
|
38
|
-
fs.existsSync(
|
|
40
|
+
fs.existsSync(
|
|
41
|
+
path.join(dirname, `/templates/${path.basename(template)}`),
|
|
42
|
+
)
|
|
39
43
|
) {
|
|
40
|
-
this.template =
|
|
44
|
+
this.template = path.join(
|
|
45
|
+
dirname,
|
|
46
|
+
`/templates/${path.basename(template)}`,
|
|
47
|
+
);
|
|
41
48
|
} else {
|
|
42
|
-
this.template =
|
|
49
|
+
this.template = path.join(dirname, `/templates/emptyTemplate`);
|
|
43
50
|
this.logger.error(
|
|
44
51
|
`Template '${template}' not found. Using 'emptyTemplate' as a fallback`,
|
|
45
52
|
);
|
|
@@ -59,7 +66,8 @@ class Mail extends Base {
|
|
|
59
66
|
* @param {object} templateData
|
|
60
67
|
* @returns string
|
|
61
68
|
*/
|
|
62
|
-
|
|
69
|
+
// eslint-disable-next-line class-methods-use-this
|
|
70
|
+
async #renderTemplateFile({ type, fullPath } = {}, templateData = {}) {
|
|
63
71
|
if (!type) {
|
|
64
72
|
return null;
|
|
65
73
|
}
|
|
@@ -109,19 +117,10 @@ class Mail extends Base {
|
|
|
109
117
|
|
|
110
118
|
const [htmlRendered, subjectRendered, textRendered, extraCss] =
|
|
111
119
|
await Promise.all([
|
|
112
|
-
this
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
),
|
|
116
|
-
this.constructor.#renderTemplateFile(
|
|
117
|
-
templates.subject,
|
|
118
|
-
templateDataToRender,
|
|
119
|
-
),
|
|
120
|
-
this.constructor.#renderTemplateFile(
|
|
121
|
-
templates.text,
|
|
122
|
-
templateDataToRender,
|
|
123
|
-
),
|
|
124
|
-
this.constructor.#renderTemplateFile(templates.style),
|
|
120
|
+
this.#renderTemplateFile(templates.html, templateDataToRender),
|
|
121
|
+
this.#renderTemplateFile(templates.subject, templateDataToRender),
|
|
122
|
+
this.#renderTemplateFile(templates.text, templateDataToRender),
|
|
123
|
+
this.#renderTemplateFile(templates.style),
|
|
125
124
|
]);
|
|
126
125
|
|
|
127
126
|
juice.tableElements = ['TABLE'];
|
|
@@ -164,7 +163,7 @@ class Mail extends Base {
|
|
|
164
163
|
|
|
165
164
|
/**
|
|
166
165
|
* Send provided text (html) to email. Low level function. All data should be prepared before sending (like inline styles)
|
|
167
|
-
* @param {
|
|
166
|
+
* @param {import('../../../server.js').default['app']} app application
|
|
168
167
|
* @param {string} to send to
|
|
169
168
|
* @param {string} subject email topic
|
|
170
169
|
* @param {string} html hmlt body of emain
|
|
@@ -215,4 +214,4 @@ class Mail extends Base {
|
|
|
215
214
|
}
|
|
216
215
|
}
|
|
217
216
|
|
|
218
|
-
|
|
217
|
+
export default Mail;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import yup from 'yup';
|
|
2
|
+
import YupValidator from './drivers/YupValidator.js';
|
|
3
|
+
import CustomValidator from './drivers/CustomValidator.js';
|
|
4
|
+
import Base from '../../modules/Base.js';
|
|
5
5
|
|
|
6
6
|
class ValidateService extends Base {
|
|
7
7
|
constructor(app, validator) {
|
|
@@ -154,4 +154,4 @@ class ValidateService extends Base {
|
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
|
|
157
|
+
export default ValidateService;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
import yup from 'yup';
|
|
4
|
+
import ValidateService from './ValidateService.js';
|
|
5
|
+
import YupValidator from './drivers/YupValidator.js';
|
|
6
|
+
import CustomValidator from './drivers/CustomValidator.js';
|
|
7
7
|
|
|
8
8
|
describe('validate service', () => {
|
|
9
9
|
describe('validateSchema funtion', () => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import Base from '../../../modules/Base.js';
|
|
2
2
|
|
|
3
3
|
class AbstractValidator extends Base {
|
|
4
4
|
constructor(app, body) {
|
|
@@ -34,4 +34,4 @@ class AbstractValidator extends Base {
|
|
|
34
34
|
return 'AbstractValidator_';
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
|
-
|
|
37
|
+
export default AbstractValidator;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const AbstractValidator = require('./AbstractValidator');
|
|
1
|
+
import yup from 'yup';
|
|
2
|
+
import AbstractValidator from './AbstractValidator.js';
|
|
4
3
|
|
|
5
4
|
class CustomValidator extends AbstractValidator {
|
|
6
5
|
async validateFields(data, { query, body, appInfo }) {
|
|
@@ -49,4 +48,4 @@ class CustomValidator extends AbstractValidator {
|
|
|
49
48
|
}
|
|
50
49
|
}
|
|
51
50
|
|
|
52
|
-
|
|
51
|
+
export default CustomValidator;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import yup from 'yup';
|
|
2
|
+
import AbstractValidator from './AbstractValidator.js';
|
|
3
3
|
|
|
4
4
|
class YupValidator extends AbstractValidator {
|
|
5
5
|
get fieldsInJsonFormat() {
|
|
@@ -100,4 +100,4 @@ class YupValidator extends AbstractValidator {
|
|
|
100
100
|
return 'YupValidator_';
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
|
-
|
|
103
|
+
export default YupValidator;
|
package/tests/setup.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
/* eslint-disable no-undef */
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { randomBytes } from 'node:crypto';
|
|
4
|
+
import { MongoMemoryReplSet } from 'mongodb-memory-server';
|
|
5
|
+
import mongoose from 'mongoose';
|
|
6
|
+
import redis from 'redis';
|
|
7
|
+
import Server from '../server.js';
|
|
8
|
+
|
|
9
|
+
import clearRedisNamespace from '../helpers/redis/clearNamespace.js';
|
|
5
10
|
|
|
6
11
|
mongoose.set('autoIndex', false);
|
|
7
12
|
|
|
8
13
|
let mongoMemoryServerInstance;
|
|
9
14
|
|
|
10
|
-
const redis = require('redis');
|
|
11
|
-
const Server = require('../server');
|
|
12
|
-
|
|
13
|
-
const clearRedisNamespace = require('../helpers/redis/clearNamespace');
|
|
14
|
-
|
|
15
15
|
jest.setTimeout(1000000);
|
|
16
16
|
beforeAll(async () => {
|
|
17
17
|
mongoMemoryServerInstance = await MongoMemoryReplSet.create({
|
|
@@ -20,6 +20,8 @@ beforeAll(async () => {
|
|
|
20
20
|
});
|
|
21
21
|
await mongoMemoryServerInstance.waitUntilRunning();
|
|
22
22
|
process.env.LOGGER_CONSOLE_LEVEL = 'error';
|
|
23
|
+
process.env.AUTH_SALT = randomBytes(16).toString('hex');
|
|
24
|
+
|
|
23
25
|
const connectionStringMongo = await mongoMemoryServerInstance.getUri();
|
|
24
26
|
// console.info('MONGO_URI: ', connectionStringMongo);
|
|
25
27
|
global.server = new Server({
|
|
@@ -28,7 +30,6 @@ beforeAll(async () => {
|
|
|
28
30
|
controllers:
|
|
29
31
|
process.env.TEST_FOLDER_CONTROLLERS || path.resolve('./controllers'),
|
|
30
32
|
views: process.env.TEST_FOLDER_VIEWS || path.resolve('./views'),
|
|
31
|
-
public: process.env.TEST_FOLDER_PUBLIC || path.resolve('./public'),
|
|
32
33
|
models: process.env.TEST_FOLDER_MODELS || path.resolve('./models'),
|
|
33
34
|
emails:
|
|
34
35
|
process.env.TEST_FOLDER_EMAIL ||
|