@adaptivestone/framework 5.0.0-alpha.2 → 5.0.0-alpha.21
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/CHANGELOG.md +96 -0
- package/commands/CreateUser.js +3 -1
- package/commands/GenerateRandomBytes.js +15 -0
- package/commands/migration/Migrate.js +1 -1
- package/config/auth.js +5 -1
- package/config/ipDetector.js +14 -0
- package/controllers/Auth.js +2 -2
- package/controllers/Home.js +1 -1
- package/folderConfig.js +0 -1
- package/helpers/files.js +8 -8
- package/jsconfig.json +9 -0
- package/models/User.js +7 -1
- package/modules/AbstractCommand.js +2 -1
- package/modules/AbstractController.js +22 -18
- package/modules/AbstractModel.d.ts +48 -0
- package/modules/AbstractModel.js +20 -2
- package/modules/Base.d.ts +5 -4
- package/modules/Base.js +11 -1
- package/package.json +13 -16
- package/server.d.ts +7 -5
- package/server.js +11 -7
- package/services/cache/Cache.d.ts +2 -2
- package/services/cache/Cache.js +10 -6
- package/services/http/HttpServer.js +12 -20
- package/services/http/middleware/GetUserByToken.js +3 -2
- package/services/http/middleware/I18n.js +20 -21
- package/services/http/middleware/IpDetector.js +59 -0
- package/services/http/middleware/Pagination.js +3 -2
- package/services/http/middleware/RateLimiter.js +8 -2
- package/services/http/middleware/RequestLogger.js +3 -3
- package/services/http/middleware/RequestParser.js +5 -2
- package/services/messaging/email/index.js +7 -15
- package/services/validate/ValidateService.js +1 -1
- package/tests/setup.js +3 -1
- package/tests/setupVitest.js +1 -1
- package/types/TFoldersConfig.d.ts +0 -2
- package/.eslintrc.cjs +0 -41
- package/controllers/Auth.test.js +0 -451
- package/controllers/Home.test.js +0 -12
- package/models/Migration.test.js +0 -20
- package/models/Sequence.test.js +0 -43
- package/models/User.test.js +0 -143
- package/modules/Modules.test.js +0 -18
- package/services/cache/Cache.test.js +0 -81
- package/services/http/middleware/Auth.test.js +0 -57
- package/services/http/middleware/Cors.test.js +0 -147
- package/services/http/middleware/GetUserByToken.test.js +0 -108
- package/services/http/middleware/I18n.test.js +0 -96
- package/services/http/middleware/PrepareAppInfo.test.js +0 -26
- package/services/http/middleware/RateLimiter.test.js +0 -233
- package/services/http/middleware/RequestParser.test.js +0 -112
- package/services/http/middleware/Role.test.js +0 -93
- package/services/http/middleware/StaticFiles.js +0 -59
- package/services/validate/ValidateService.test.js +0 -107
package/server.js
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
2
|
import EventEmitter from 'node:events';
|
|
3
|
-
import { hrtime } from 'node:process';
|
|
3
|
+
import { hrtime, loadEnvFile } from 'node:process';
|
|
4
4
|
import * as url from 'node:url';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
|
|
7
|
-
import 'dotenv/config';
|
|
8
7
|
import merge from 'deepmerge';
|
|
9
8
|
import winston from 'winston';
|
|
10
9
|
import { getFilesPathWithInheritance } from './helpers/files.js';
|
|
11
10
|
import { consoleLogger } from './helpers/logger.js';
|
|
12
11
|
import Cache from './services/cache/Cache.js';
|
|
13
12
|
|
|
13
|
+
try {
|
|
14
|
+
loadEnvFile();
|
|
15
|
+
} catch (e) {
|
|
16
|
+
console.warn('No env file found. This is ok. But please check youself.');
|
|
17
|
+
}
|
|
18
|
+
|
|
14
19
|
/**
|
|
15
20
|
* Main framework class.
|
|
16
21
|
*/
|
|
@@ -19,6 +24,8 @@ class Server {
|
|
|
19
24
|
|
|
20
25
|
#isInited = false;
|
|
21
26
|
|
|
27
|
+
cli = null;
|
|
28
|
+
|
|
22
29
|
/**
|
|
23
30
|
* Construct new server
|
|
24
31
|
* @param {Object} config main config object
|
|
@@ -27,7 +34,6 @@ class Server {
|
|
|
27
34
|
* @param {String} config.folders.models path to folder with moidels files
|
|
28
35
|
* @param {String} config.folders.controllers path to folder with controllers files
|
|
29
36
|
* @param {String} config.folders.views path to folder with view files
|
|
30
|
-
* @param {String} config.folders.public path to folder with public files
|
|
31
37
|
* @param {String} config.folders.locales path to folder with locales files
|
|
32
38
|
* @param {String} config.folders.emails path to folder with emails files
|
|
33
39
|
*/
|
|
@@ -56,13 +62,11 @@ class Server {
|
|
|
56
62
|
models: new Map(),
|
|
57
63
|
modelConstructors: new Map(),
|
|
58
64
|
};
|
|
59
|
-
|
|
60
|
-
this.cli = false;
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
/**
|
|
64
68
|
* Start server (http + init all http ralated functions)
|
|
65
|
-
* @param
|
|
69
|
+
* @param {Function} callbackBefore404 code that should be executed before adding page 404
|
|
66
70
|
* @returns {Promise}
|
|
67
71
|
*/
|
|
68
72
|
async startServer(callbackBefore404 = async () => Promise.resolve()) {
|
|
@@ -350,7 +354,7 @@ class Server {
|
|
|
350
354
|
* Return model from {modelName} (file name) on model folder.
|
|
351
355
|
* Support cache
|
|
352
356
|
* @param {String} modelName name on config file to load
|
|
353
|
-
* @returns {import('mongoose').Model}
|
|
357
|
+
* @returns {import('mongoose').Model | false| {}}
|
|
354
358
|
*/
|
|
355
359
|
getModel(modelName) {
|
|
356
360
|
if (modelName.endsWith('s')) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Base from '../../modules/Base';
|
|
2
|
-
import Server from '../../server';
|
|
2
|
+
import Server from '../../server.js';
|
|
3
3
|
|
|
4
4
|
declare class Cache extends Base {
|
|
5
5
|
app: Server['app'];
|
|
@@ -32,4 +32,4 @@ declare class Cache extends Base {
|
|
|
32
32
|
removeKey(key: string): Promise<number>;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
export
|
|
35
|
+
export default Cache;
|
package/services/cache/Cache.js
CHANGED
|
@@ -43,8 +43,10 @@ class Cache extends Base {
|
|
|
43
43
|
}
|
|
44
44
|
const key = this.getKeyWithNameSpace(keyValue);
|
|
45
45
|
// 5 mins default
|
|
46
|
-
|
|
47
|
-
let
|
|
46
|
+
// eslint-disable-next-line no-unused-vars
|
|
47
|
+
let resolve = (value) => {};
|
|
48
|
+
// eslint-disable-next-line no-unused-vars
|
|
49
|
+
let reject = (value) => {};
|
|
48
50
|
if (this.promiseMapping.has(key)) {
|
|
49
51
|
return this.promiseMapping.get(key);
|
|
50
52
|
}
|
|
@@ -69,12 +71,14 @@ class Cache extends Base {
|
|
|
69
71
|
return Promise.reject(e);
|
|
70
72
|
}
|
|
71
73
|
|
|
72
|
-
this.redisClient.
|
|
74
|
+
this.redisClient.set(
|
|
73
75
|
key,
|
|
74
|
-
|
|
75
|
-
JSON.stringify(result, (jsonkey, value) =>
|
|
76
|
+
JSON.stringify(result, (_jsonkey, value) =>
|
|
76
77
|
typeof value === 'bigint' ? `${value}n` : value,
|
|
77
78
|
),
|
|
79
|
+
{
|
|
80
|
+
EX: storeTime,
|
|
81
|
+
},
|
|
78
82
|
);
|
|
79
83
|
} else {
|
|
80
84
|
this.logger.verbose(
|
|
@@ -84,7 +88,7 @@ class Cache extends Base {
|
|
|
84
88
|
)}`,
|
|
85
89
|
);
|
|
86
90
|
try {
|
|
87
|
-
result = JSON.parse(result, (
|
|
91
|
+
result = JSON.parse(result, (_jsonkey, value) => {
|
|
88
92
|
if (typeof value === 'string' && /^\d+n$/.test(value)) {
|
|
89
93
|
return BigInt(value.slice(0, value.length - 1));
|
|
90
94
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import http from 'node:http';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import * as url from 'node:url';
|
|
2
|
+
// import path from 'node:path';
|
|
3
|
+
// import * as url from 'node:url';
|
|
4
4
|
import express from 'express';
|
|
5
5
|
import RequestLoggerMiddleware from './middleware/RequestLogger.js';
|
|
6
6
|
import I18nMiddleware from './middleware/I18n.js';
|
|
7
7
|
import PrepareAppInfoMiddleware from './middleware/PrepareAppInfo.js';
|
|
8
8
|
import RequestParserMiddleware from './middleware/RequestParser.js';
|
|
9
|
-
import
|
|
9
|
+
import IpDetector from './middleware/IpDetector.js';
|
|
10
10
|
import Cors from './middleware/Cors.js';
|
|
11
11
|
import Base from '../../modules/Base.js';
|
|
12
12
|
|
|
@@ -18,15 +18,16 @@ class HttpServer extends Base {
|
|
|
18
18
|
super(app);
|
|
19
19
|
this.express = express();
|
|
20
20
|
this.express.disable('x-powered-by');
|
|
21
|
-
const dirname = url.fileURLToPath(new URL('.', import.meta.url));
|
|
22
|
-
this.express.set('views', [
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
]);
|
|
26
|
-
this.express.set('view engine', 'pug');
|
|
21
|
+
// const dirname = url.fileURLToPath(new URL('.', import.meta.url));
|
|
22
|
+
// this.express.set('views', [
|
|
23
|
+
// this.app.foldersConfig.views,
|
|
24
|
+
// path.join(dirname, '../../views'),
|
|
25
|
+
// ]);
|
|
26
|
+
// this.express.set('view engine', 'pug');
|
|
27
27
|
|
|
28
|
-
this.express.use(new PrepareAppInfoMiddleware(this.app).getMiddleware());
|
|
29
28
|
this.express.use(new RequestLoggerMiddleware(this.app).getMiddleware());
|
|
29
|
+
this.express.use(new PrepareAppInfoMiddleware(this.app).getMiddleware());
|
|
30
|
+
this.express.use(new IpDetector(this.app).getMiddleware());
|
|
30
31
|
this.express.use(new I18nMiddleware(this.app).getMiddleware());
|
|
31
32
|
|
|
32
33
|
const httpConfig = this.app.getConfig('http');
|
|
@@ -35,15 +36,6 @@ class HttpServer extends Base {
|
|
|
35
36
|
origins: httpConfig.corsDomains,
|
|
36
37
|
}).getMiddleware(),
|
|
37
38
|
);
|
|
38
|
-
// todo whitelist
|
|
39
|
-
this.express.use(
|
|
40
|
-
new StaticFilesMiddleware(this.app, {
|
|
41
|
-
folders: [
|
|
42
|
-
this.app.foldersConfig.public,
|
|
43
|
-
path.join(dirname, '../../public/files'),
|
|
44
|
-
],
|
|
45
|
-
}).getMiddleware(),
|
|
46
|
-
);
|
|
47
39
|
|
|
48
40
|
this.express.use(new RequestParserMiddleware(this.app).getMiddleware());
|
|
49
41
|
|
|
@@ -57,7 +49,7 @@ class HttpServer extends Base {
|
|
|
57
49
|
res.status(500).json({ message: 'Something broke!' });
|
|
58
50
|
});
|
|
59
51
|
|
|
60
|
-
this.httpServer = http.
|
|
52
|
+
this.httpServer = http.createServer(this.express);
|
|
61
53
|
|
|
62
54
|
const listener = this.httpServer.listen(
|
|
63
55
|
httpConfig.port,
|
|
@@ -5,13 +5,14 @@ class GetUserByToken extends AbstractMiddleware {
|
|
|
5
5
|
return 'Grab a token and try to parse the user from it. It user exist will add req.appInfo.user variable';
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
// eslint-disable-next-line class-methods-use-this
|
|
8
9
|
get usedAuthParameters() {
|
|
9
10
|
return [
|
|
10
11
|
{
|
|
11
12
|
name: 'Authorization',
|
|
12
13
|
type: 'apiKey',
|
|
13
14
|
in: 'header',
|
|
14
|
-
description:
|
|
15
|
+
description: GetUserByToken.description,
|
|
15
16
|
},
|
|
16
17
|
];
|
|
17
18
|
}
|
|
@@ -21,7 +22,7 @@ class GetUserByToken extends AbstractMiddleware {
|
|
|
21
22
|
this.logger.warn('You call GetUserByToken more then once');
|
|
22
23
|
return next();
|
|
23
24
|
}
|
|
24
|
-
let { token } = req.body;
|
|
25
|
+
let { token } = req.body || {};
|
|
25
26
|
this.logger.verbose(
|
|
26
27
|
`GetUserByToken token in BODY ${token}. Token in Authorization header ${req.get(
|
|
27
28
|
'Authorization',
|
|
@@ -1,37 +1,36 @@
|
|
|
1
1
|
import i18next from 'i18next';
|
|
2
2
|
import BackendFS from 'i18next-fs-backend';
|
|
3
|
-
import Backend from 'i18next-chained-backend';
|
|
4
3
|
import AbstractMiddleware from './AbstractMiddleware.js';
|
|
5
4
|
|
|
6
5
|
class I18n extends AbstractMiddleware {
|
|
6
|
+
cache = {};
|
|
7
|
+
|
|
8
|
+
enabled = true;
|
|
9
|
+
|
|
10
|
+
lookupQuerystring = '';
|
|
11
|
+
|
|
12
|
+
supportedLngs = [];
|
|
13
|
+
|
|
14
|
+
fallbackLng = 'en';
|
|
15
|
+
|
|
16
|
+
/** @type {i18next} */
|
|
17
|
+
i18n = {
|
|
18
|
+
// @ts-ignore
|
|
19
|
+
t: (text) => text,
|
|
20
|
+
language: 'en',
|
|
21
|
+
};
|
|
22
|
+
|
|
7
23
|
constructor(app, params) {
|
|
8
24
|
super(app, params);
|
|
9
25
|
const I18NConfig = this.app.getConfig('i18n');
|
|
10
|
-
this.i18n = {
|
|
11
|
-
t: (text) => text,
|
|
12
|
-
language: I18NConfig.fallbackLng,
|
|
13
|
-
};
|
|
14
|
-
this.cache = {};
|
|
15
26
|
|
|
16
27
|
if (I18NConfig.enabled) {
|
|
17
28
|
this.logger.info('Enabling i18n support');
|
|
18
29
|
this.i18n = i18next;
|
|
19
|
-
i18next.use(
|
|
30
|
+
i18next.use(BackendFS).init({
|
|
20
31
|
backend: {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
// BackendFS,
|
|
24
|
-
],
|
|
25
|
-
backendOptions: [
|
|
26
|
-
// {
|
|
27
|
-
// loadPath: __dirname + '/../../locales/{{lng}}/{{ns}}.json',
|
|
28
|
-
// addPath: __dirname + '/../../locales/{{lng}}/{{ns}}.missing.json'
|
|
29
|
-
// },
|
|
30
|
-
{
|
|
31
|
-
loadPath: `${this.app.foldersConfig.locales}/{{lng}}/{{ns}}.json`,
|
|
32
|
-
addPath: `${this.app.foldersConfig.locales}/{{lng}}/{{ns}}.missing.json`,
|
|
33
|
-
},
|
|
34
|
-
],
|
|
32
|
+
loadPath: `${this.app.foldersConfig.locales}/{{lng}}/{{ns}}.json`,
|
|
33
|
+
addPath: `${this.app.foldersConfig.locales}/{{lng}}/{{ns}}.missing.json`,
|
|
35
34
|
},
|
|
36
35
|
fallbackLng: I18NConfig.fallbackLng,
|
|
37
36
|
preload: I18NConfig.preload,
|
|
@@ -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;
|
|
@@ -19,8 +19,9 @@ class Pagination extends AbstractMiddleware {
|
|
|
19
19
|
async middleware(req, res, next) {
|
|
20
20
|
let { limit, maxLimit } = this.params;
|
|
21
21
|
|
|
22
|
-
limit = typeof limit
|
|
23
|
-
maxLimit =
|
|
22
|
+
limit = (typeof limit !== 'number' ? parseInt(limit, 10) : limit) || 10;
|
|
23
|
+
maxLimit =
|
|
24
|
+
(typeof maxLimit !== 'number' ? parseInt(maxLimit, 10) : maxLimit) || 100;
|
|
24
25
|
|
|
25
26
|
req.appInfo.pagination = {};
|
|
26
27
|
req.appInfo.pagination.page =
|
|
@@ -78,10 +78,16 @@ class RateLimiter extends AbstractMiddleware {
|
|
|
78
78
|
|
|
79
79
|
const key = [];
|
|
80
80
|
if (ip) {
|
|
81
|
-
|
|
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
|
+
}
|
|
82
88
|
}
|
|
83
89
|
if (route) {
|
|
84
|
-
key.push(req.
|
|
90
|
+
key.push(`${req.baseUrl ?? ''}${req.path ?? ''}`); // to avoid quesry params
|
|
85
91
|
}
|
|
86
92
|
if (user && req.appInfo?.user) {
|
|
87
93
|
key.push(req.appInfo?.user.id);
|
|
@@ -6,13 +6,13 @@ class RequestLogger extends AbstractMiddleware {
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
async middleware(req, res, next) {
|
|
9
|
-
const startTime =
|
|
9
|
+
const startTime = performance.now();
|
|
10
10
|
const text = `Request is [${req.method}] ${req.url}`;
|
|
11
11
|
this.logger.info(text);
|
|
12
12
|
res.on('finish', () => {
|
|
13
|
-
const
|
|
13
|
+
const end = performance.now();
|
|
14
14
|
this.logger.info(
|
|
15
|
-
`Finished ${text}. Status: ${res.statusCode}.
|
|
15
|
+
`Finished ${text}. Status: ${res.statusCode}. [${(end - startTime).toFixed(2)} ms]`,
|
|
16
16
|
);
|
|
17
17
|
});
|
|
18
18
|
next();
|
|
@@ -18,7 +18,10 @@ class RequestParser extends AbstractMiddleware {
|
|
|
18
18
|
[fields, files] = await form.parse(req);
|
|
19
19
|
} catch (err) {
|
|
20
20
|
this.logger.error(`Parsing failed ${err}`);
|
|
21
|
-
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);
|
|
22
25
|
}
|
|
23
26
|
this.logger.verbose(
|
|
24
27
|
`Parsing multipart/formdata request DONE ${Date.now() - time}ms`,
|
|
@@ -26,7 +29,7 @@ class RequestParser extends AbstractMiddleware {
|
|
|
26
29
|
|
|
27
30
|
req.body = {
|
|
28
31
|
// todo avoid body in next versions
|
|
29
|
-
...req.body,
|
|
32
|
+
...(req.body || {}),
|
|
30
33
|
...fields,
|
|
31
34
|
...files,
|
|
32
35
|
};
|
|
@@ -66,7 +66,8 @@ class Mail extends Base {
|
|
|
66
66
|
* @param {object} templateData
|
|
67
67
|
* @returns string
|
|
68
68
|
*/
|
|
69
|
-
|
|
69
|
+
// eslint-disable-next-line class-methods-use-this
|
|
70
|
+
async #renderTemplateFile({ type, fullPath } = {}, templateData = {}) {
|
|
70
71
|
if (!type) {
|
|
71
72
|
return null;
|
|
72
73
|
}
|
|
@@ -116,19 +117,10 @@ class Mail extends Base {
|
|
|
116
117
|
|
|
117
118
|
const [htmlRendered, subjectRendered, textRendered, extraCss] =
|
|
118
119
|
await Promise.all([
|
|
119
|
-
this
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
),
|
|
123
|
-
this.constructor.#renderTemplateFile(
|
|
124
|
-
templates.subject,
|
|
125
|
-
templateDataToRender,
|
|
126
|
-
),
|
|
127
|
-
this.constructor.#renderTemplateFile(
|
|
128
|
-
templates.text,
|
|
129
|
-
templateDataToRender,
|
|
130
|
-
),
|
|
131
|
-
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),
|
|
132
124
|
]);
|
|
133
125
|
|
|
134
126
|
juice.tableElements = ['TABLE'];
|
|
@@ -171,7 +163,7 @@ class Mail extends Base {
|
|
|
171
163
|
|
|
172
164
|
/**
|
|
173
165
|
* Send provided text (html) to email. Low level function. All data should be prepared before sending (like inline styles)
|
|
174
|
-
* @param {
|
|
166
|
+
* @param {import('../../../server.js').default['app']} app application
|
|
175
167
|
* @param {string} to send to
|
|
176
168
|
* @param {string} subject email topic
|
|
177
169
|
* @param {string} html hmlt body of emain
|
package/tests/setup.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* eslint-disable no-undef */
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { randomBytes } from 'node:crypto';
|
|
3
4
|
import { MongoMemoryReplSet } from 'mongodb-memory-server';
|
|
4
5
|
import mongoose from 'mongoose';
|
|
5
6
|
import redis from 'redis';
|
|
@@ -19,6 +20,8 @@ beforeAll(async () => {
|
|
|
19
20
|
});
|
|
20
21
|
await mongoMemoryServerInstance.waitUntilRunning();
|
|
21
22
|
process.env.LOGGER_CONSOLE_LEVEL = 'error';
|
|
23
|
+
process.env.AUTH_SALT = randomBytes(16).toString('hex');
|
|
24
|
+
|
|
22
25
|
const connectionStringMongo = await mongoMemoryServerInstance.getUri();
|
|
23
26
|
// console.info('MONGO_URI: ', connectionStringMongo);
|
|
24
27
|
global.server = new Server({
|
|
@@ -27,7 +30,6 @@ beforeAll(async () => {
|
|
|
27
30
|
controllers:
|
|
28
31
|
process.env.TEST_FOLDER_CONTROLLERS || path.resolve('./controllers'),
|
|
29
32
|
views: process.env.TEST_FOLDER_VIEWS || path.resolve('./views'),
|
|
30
|
-
public: process.env.TEST_FOLDER_PUBLIC || path.resolve('./public'),
|
|
31
33
|
models: process.env.TEST_FOLDER_MODELS || path.resolve('./models'),
|
|
32
34
|
emails:
|
|
33
35
|
process.env.TEST_FOLDER_EMAIL ||
|
package/tests/setupVitest.js
CHANGED
|
@@ -12,13 +12,13 @@ mongoose.set('autoIndex', false);
|
|
|
12
12
|
|
|
13
13
|
beforeAll(async () => {
|
|
14
14
|
process.env.LOGGER_CONSOLE_LEVEL = 'error';
|
|
15
|
+
process.env.AUTH_SALT = crypto.randomBytes(16).toString('hex');
|
|
15
16
|
global.server = new Server({
|
|
16
17
|
folders: {
|
|
17
18
|
config: process.env.TEST_FOLDER_CONFIG || path.resolve('./config'),
|
|
18
19
|
controllers:
|
|
19
20
|
process.env.TEST_FOLDER_CONTROLLERS || path.resolve('./controllers'),
|
|
20
21
|
views: process.env.TEST_FOLDER_VIEWS || path.resolve('./views'),
|
|
21
|
-
public: process.env.TEST_FOLDER_PUBLIC || path.resolve('./public'),
|
|
22
22
|
models: process.env.TEST_FOLDER_MODELS || path.resolve('./models'),
|
|
23
23
|
emails:
|
|
24
24
|
process.env.TEST_FOLDER_EMAIL ||
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* @param models path to folder with moidels files
|
|
4
4
|
* @param controllers path to folder with controllers files
|
|
5
5
|
* @param views path to folder with view files
|
|
6
|
-
* @param public path to folder with public files
|
|
7
6
|
* @param locales path to folder with locales files
|
|
8
7
|
* @param emails path to folder with emails files
|
|
9
8
|
*/
|
|
@@ -12,7 +11,6 @@ type FolderConfig = {
|
|
|
12
11
|
models: string;
|
|
13
12
|
controllers: string;
|
|
14
13
|
views: string;
|
|
15
|
-
public: string;
|
|
16
14
|
emails: string;
|
|
17
15
|
};
|
|
18
16
|
|
package/.eslintrc.cjs
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
module.exports = {
|
|
2
|
-
env: {
|
|
3
|
-
es2023: true,
|
|
4
|
-
node: true,
|
|
5
|
-
},
|
|
6
|
-
extends: ['airbnb-base', 'prettier'],
|
|
7
|
-
plugins: ['prettier'],
|
|
8
|
-
parserOptions: {
|
|
9
|
-
ecmaVersion: 2023,
|
|
10
|
-
},
|
|
11
|
-
rules: {
|
|
12
|
-
'no-restricted-syntax': [
|
|
13
|
-
'error',
|
|
14
|
-
{
|
|
15
|
-
selector: 'ForInStatement',
|
|
16
|
-
message:
|
|
17
|
-
'for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array.',
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
selector: 'LabeledStatement',
|
|
21
|
-
message:
|
|
22
|
-
'Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand.',
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
selector: 'WithStatement',
|
|
26
|
-
message:
|
|
27
|
-
'`with` is disallowed in strict mode because it makes code impossible to predict and optimize.',
|
|
28
|
-
},
|
|
29
|
-
],
|
|
30
|
-
'prettier/prettier': 'error',
|
|
31
|
-
'import/extensions': 'off', // it have a problem with dynamic imports
|
|
32
|
-
'import/prefer-default-export': 'off', // we want to have signgke default export too
|
|
33
|
-
},
|
|
34
|
-
overrides: [
|
|
35
|
-
{
|
|
36
|
-
files: ['**/*.test.js'],
|
|
37
|
-
extends: ['plugin:vitest/all', 'plugin:vitest/recommended'],
|
|
38
|
-
plugins: ['vitest'],
|
|
39
|
-
},
|
|
40
|
-
],
|
|
41
|
-
};
|