@adaptivestone/framework 3.4.2 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/LICENCE +21 -0
- package/cluster.js +3 -3
- package/commands/CreateUser.js +27 -0
- package/commands/Documentation.js +1 -1
- package/commands/GetOpenApiJson.js +53 -23
- package/commands/migration/Create.js +2 -2
- package/config/auth.js +1 -1
- package/config/i18n.js +4 -3
- package/config/mail.js +5 -1
- package/controllers/Home.js +2 -2
- package/controllers/Home.test.js +11 -0
- package/controllers/index.js +15 -15
- package/folderConfig.js +1 -1
- package/helpers/yup.js +24 -0
- package/index.js +8 -0
- package/models/User.js +38 -27
- package/models/User.test.js +68 -18
- package/modules/AbstractController.js +144 -208
- package/modules/AbstractModel.js +2 -1
- package/modules/Base.js +3 -2
- package/modules/BaseCli.js +6 -2
- package/package.json +18 -14
- package/server.d.ts +1 -1
- package/server.js +25 -8
- package/services/cache/Cache.d.ts +3 -3
- package/services/cache/Cache.js +17 -3
- package/services/documentation/DocumentationGenerator.js +171 -0
- package/services/http/HttpServer.js +16 -96
- package/services/http/middleware/AbstractMiddleware.js +20 -0
- package/services/http/middleware/GetUserByToken.js +4 -0
- package/services/http/middleware/I18n.js +119 -0
- package/services/http/middleware/I18n.test.js +77 -0
- package/services/http/middleware/Pagination.js +56 -0
- package/services/http/middleware/PrepareAppInfo.test.js +22 -0
- package/services/http/middleware/{Middlewares.test.js → RateLimiter.test.js} +1 -1
- package/services/http/middleware/RequestLogger.js +22 -0
- package/services/http/middleware/RequestParser.js +36 -0
- package/services/messaging/email/index.js +141 -41
- package/services/messaging/email/resources/.gitkeep +1 -0
- package/services/validate/ValidateService.js +161 -0
- package/services/validate/ValidateService.test.js +105 -0
- package/services/validate/drivers/AbstractValidator.js +37 -0
- package/services/validate/drivers/CustomValidator.js +52 -0
- package/services/validate/drivers/YupValidator.js +103 -0
- package/tests/setup.js +13 -5
- package/services/messaging/email/templates/emptyTemplate/style.less +0 -0
- package/services/messaging/email/templates/password/html.handlebars +0 -13
- package/services/messaging/email/templates/password/style.less +0 -0
- package/services/messaging/email/templates/password/subject.handlebars +0 -1
- package/services/messaging/email/templates/password/text.handlebars +0 -1
- package/services/messaging/email/templates/verification/style.less +0 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const PrepareAppInfo = require('./PrepareAppInfo');
|
|
2
|
+
|
|
3
|
+
describe('prepareAppInfo methods', () => {
|
|
4
|
+
it('have description fields', async () => {
|
|
5
|
+
expect.assertions(1);
|
|
6
|
+
const middleware = new PrepareAppInfo(global.server.app);
|
|
7
|
+
expect(middleware.constructor.description).toBeDefined();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('middleware that works', async () => {
|
|
11
|
+
expect.assertions(3);
|
|
12
|
+
const middleware = new PrepareAppInfo(global.server.app);
|
|
13
|
+
const nextFunction = jest.fn(() => {});
|
|
14
|
+
const req = {};
|
|
15
|
+
await middleware.middleware(req, {}, nextFunction);
|
|
16
|
+
expect(nextFunction).toHaveBeenCalledWith();
|
|
17
|
+
expect(req.appInfo).toBeDefined();
|
|
18
|
+
req.appInfo.test = 5;
|
|
19
|
+
await middleware.middleware(req, {}, nextFunction);
|
|
20
|
+
expect(req.appInfo.test).toBe(5);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const AbstractMiddleware = require('./AbstractMiddleware');
|
|
2
|
+
|
|
3
|
+
class RequestLogger extends AbstractMiddleware {
|
|
4
|
+
static get description() {
|
|
5
|
+
return 'Log info about the request';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
async middleware(req, res, next) {
|
|
9
|
+
const startTime = Date.now();
|
|
10
|
+
const text = `Request is [${req.method}] ${req.url}`;
|
|
11
|
+
this.logger.info(text);
|
|
12
|
+
res.on('finish', () => {
|
|
13
|
+
const duration = Date.now() - startTime;
|
|
14
|
+
this.logger.info(
|
|
15
|
+
`Finished ${text}. Status: ${res.statusCode}. Duration ${duration} ms`,
|
|
16
|
+
);
|
|
17
|
+
});
|
|
18
|
+
next();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = RequestLogger;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const formidable = require('formidable');
|
|
2
|
+
|
|
3
|
+
const AbstractMiddleware = require('./AbstractMiddleware');
|
|
4
|
+
|
|
5
|
+
class RequestParser extends AbstractMiddleware {
|
|
6
|
+
static get description() {
|
|
7
|
+
return 'Parses incoming request. Based on Formidable library';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async middleware(req, res, next) {
|
|
11
|
+
const time = Date.now();
|
|
12
|
+
this.logger.verbose(`Parsing request`);
|
|
13
|
+
// TODO update this to https://github.com/node-formidable/formidable/issues/412#issuecomment-1367914268 in node v20 (in 2023?)
|
|
14
|
+
|
|
15
|
+
const form = formidable(this.params); // not in construstor as reuse formidable affects performance
|
|
16
|
+
form.parse(req, (err, fields, files) => {
|
|
17
|
+
this.logger.verbose(
|
|
18
|
+
`Parsing multipart/formdata request DONE ${Date.now() - time}ms`,
|
|
19
|
+
);
|
|
20
|
+
if (err) {
|
|
21
|
+
this.logger.error(`Parsing failed ${err}`);
|
|
22
|
+
return next(err);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
req.body = {
|
|
26
|
+
// todo avoid body in next versions
|
|
27
|
+
...req.body,
|
|
28
|
+
...fields,
|
|
29
|
+
...files,
|
|
30
|
+
};
|
|
31
|
+
return next();
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = RequestParser;
|
|
@@ -1,21 +1,29 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const
|
|
1
|
+
const fs = require('node:fs');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
const { promisify } = require('node:util');
|
|
3
4
|
const nodemailer = require('nodemailer');
|
|
4
5
|
const sendMail = require('nodemailer-sendmail-transport');
|
|
5
6
|
const stub = require('nodemailer-stub-transport');
|
|
7
|
+
const pug = require('pug');
|
|
8
|
+
const juice = require('juice');
|
|
9
|
+
const { convert } = require('html-to-text');
|
|
6
10
|
|
|
7
11
|
const mailTransports = {
|
|
8
12
|
sendMail,
|
|
9
13
|
stub,
|
|
10
14
|
smtp: (data) => data,
|
|
11
15
|
};
|
|
12
|
-
const path = require('path');
|
|
13
16
|
const Base = require('../../../modules/Base');
|
|
14
17
|
|
|
15
|
-
// const i18next = require('i18next');
|
|
16
|
-
|
|
17
18
|
class Mail extends Base {
|
|
18
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Construct mail class
|
|
21
|
+
* @param {object} app
|
|
22
|
+
* @param {string} template template name
|
|
23
|
+
* @param {object} [templateData={}] data to render in template. Object with value that available inside template
|
|
24
|
+
* @param {object} [i18n] data to render in template
|
|
25
|
+
*/
|
|
26
|
+
constructor(app, template, templateData = {}, i18n = null) {
|
|
19
27
|
super(app);
|
|
20
28
|
if (!path.isAbsolute(template)) {
|
|
21
29
|
if (
|
|
@@ -38,60 +46,152 @@ class Mail extends Base {
|
|
|
38
46
|
}
|
|
39
47
|
}
|
|
40
48
|
this.templateData = templateData;
|
|
41
|
-
this.i18n = i18n
|
|
42
|
-
|
|
49
|
+
this.i18n = i18n ?? {
|
|
50
|
+
t: (str) => str,
|
|
51
|
+
locale: 'en', // todo change it to config
|
|
52
|
+
};
|
|
53
|
+
this.locale = this.i18n?.language;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Render template
|
|
58
|
+
* @param {object} type and fullpath
|
|
59
|
+
* @param {object} templateData
|
|
60
|
+
* @returns string
|
|
61
|
+
*/
|
|
62
|
+
static async #renderTemplate({ type, fullPath } = {}, templateData = {}) {
|
|
63
|
+
if (!type) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
switch (type) {
|
|
68
|
+
case 'html':
|
|
69
|
+
case 'text':
|
|
70
|
+
case 'css':
|
|
71
|
+
return fs.promises.readFile(fullPath, { encoding: 'utf8' });
|
|
72
|
+
case 'pug': {
|
|
73
|
+
const compiledFunction = pug.compileFile(fullPath);
|
|
74
|
+
return compiledFunction(templateData);
|
|
75
|
+
}
|
|
76
|
+
default:
|
|
77
|
+
throw new Error(`Template type ${type} is not supported`);
|
|
78
|
+
}
|
|
43
79
|
}
|
|
44
80
|
|
|
45
81
|
/**
|
|
46
82
|
* Send email
|
|
47
|
-
* @param to
|
|
48
|
-
* @param [from = mailConfig.from]
|
|
83
|
+
* @param {string} to email send to
|
|
84
|
+
* @param {string} [from = mailConfig.from]
|
|
85
|
+
* @param {object} [aditionalNodemailerOptions = {}] additional option to nodemailer
|
|
49
86
|
* @return {Promise}
|
|
50
87
|
*/
|
|
51
|
-
async send(to, from) {
|
|
88
|
+
async send(to, from = null, aditionalNodemailerOptions = {}) {
|
|
89
|
+
const files = await fs.promises.readdir(this.template);
|
|
90
|
+
const templates = {};
|
|
91
|
+
for (const file of files) {
|
|
92
|
+
const [name, extension] = file.split('.');
|
|
93
|
+
templates[name] = {
|
|
94
|
+
type: extension,
|
|
95
|
+
fullPath: path.join(this.template, file),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!templates.html || !templates.subject) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
'Template HTML and Subject must be provided. Please follow documentation for details https://framework.adaptivestone.com/docs/email',
|
|
102
|
+
);
|
|
103
|
+
}
|
|
52
104
|
const mailConfig = this.app.getConfig('mail');
|
|
105
|
+
|
|
106
|
+
const templateDataToRender = {
|
|
107
|
+
locale: this.locale,
|
|
108
|
+
t: this.i18n.t.bind(this.i18n),
|
|
109
|
+
...mailConfig.globalVariablesToTemplates,
|
|
110
|
+
...this.templateData,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const [htmlRendered, subjectRendered, textRendered, extraCss] =
|
|
114
|
+
await Promise.all([
|
|
115
|
+
this.constructor.#renderTemplate(templates.html, templateDataToRender),
|
|
116
|
+
this.constructor.#renderTemplate(
|
|
117
|
+
templates.subject,
|
|
118
|
+
templateDataToRender,
|
|
119
|
+
),
|
|
120
|
+
this.constructor.#renderTemplate(templates.text, templateDataToRender),
|
|
121
|
+
this.constructor.#renderTemplate(templates.style),
|
|
122
|
+
]);
|
|
123
|
+
|
|
124
|
+
juice.tableElements = ['TABLE'];
|
|
125
|
+
|
|
126
|
+
const juiceResourcesAsync = promisify(juice.juiceResources);
|
|
127
|
+
|
|
128
|
+
const inlinedHTML = await juiceResourcesAsync(htmlRendered, {
|
|
129
|
+
preserveImportant: true,
|
|
130
|
+
webResources: mailConfig.webResources,
|
|
131
|
+
extraCss,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
return this.constructor.sendRaw(
|
|
135
|
+
this.app,
|
|
136
|
+
to,
|
|
137
|
+
subjectRendered,
|
|
138
|
+
inlinedHTML,
|
|
139
|
+
textRendered,
|
|
140
|
+
from,
|
|
141
|
+
aditionalNodemailerOptions,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Send provided text (html) to email. Low level function. All data should be prepared before sending (like inline styles)
|
|
147
|
+
* @param {objetc} app application
|
|
148
|
+
* @param {string} to send to
|
|
149
|
+
* @param {string} subject email topic
|
|
150
|
+
* @param {string} html hmlt body of emain
|
|
151
|
+
* @param {string} [text] if not provided will be generated from html string
|
|
152
|
+
* @param {string} [from = mailConfig.from] from. If not provided will be grabbed from config
|
|
153
|
+
* @param {object} [additionalNodeMailerOption = {}] any otipns to pass to nodemailer https://nodemailer.com/message/
|
|
154
|
+
*/
|
|
155
|
+
static async sendRaw(
|
|
156
|
+
app,
|
|
157
|
+
to,
|
|
158
|
+
subject,
|
|
159
|
+
html,
|
|
160
|
+
text = null,
|
|
161
|
+
from = null,
|
|
162
|
+
additionalNodeMailerOption = {},
|
|
163
|
+
) {
|
|
164
|
+
if (!app || !to || !subject || !html) {
|
|
165
|
+
throw new Error('App, to, subject and html is required fields.');
|
|
166
|
+
}
|
|
167
|
+
const mailConfig = app.getConfig('mail');
|
|
53
168
|
if (!from) {
|
|
54
169
|
// eslint-disable-next-line no-param-reassign
|
|
55
170
|
from = mailConfig.from;
|
|
56
171
|
}
|
|
57
|
-
|
|
172
|
+
|
|
173
|
+
if (!text) {
|
|
174
|
+
// eslint-disable-next-line no-param-reassign
|
|
175
|
+
text = convert(html, {
|
|
176
|
+
selectors: [{ selector: 'img', format: 'skip' }],
|
|
177
|
+
});
|
|
178
|
+
}
|
|
58
179
|
const transportConfig = mailConfig.transports[mailConfig.transport];
|
|
59
180
|
const transport = mailTransports[mailConfig.transport];
|
|
60
181
|
const transporter = nodemailer.createTransport(transport(transportConfig));
|
|
61
182
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
juiceResources: {
|
|
70
|
-
webResources: mailConfig.webResources,
|
|
71
|
-
},
|
|
183
|
+
return transporter.sendMail({
|
|
184
|
+
from,
|
|
185
|
+
to,
|
|
186
|
+
subject,
|
|
187
|
+
text,
|
|
188
|
+
html,
|
|
189
|
+
...additionalNodeMailerOption,
|
|
72
190
|
});
|
|
73
|
-
|
|
74
|
-
return email.send({
|
|
75
|
-
template: this.template,
|
|
76
|
-
message: {
|
|
77
|
-
to,
|
|
78
|
-
},
|
|
79
|
-
locals: {
|
|
80
|
-
locale: this.locale,
|
|
81
|
-
serverDomain: mailConfig.myDomain,
|
|
82
|
-
siteDomain,
|
|
83
|
-
t: this.i18n.t.bind(this.i18n),
|
|
84
|
-
...this.templateData,
|
|
85
|
-
},
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
render() {
|
|
90
|
-
// TODO for debug
|
|
91
191
|
}
|
|
92
192
|
|
|
93
193
|
static get loggerGroup() {
|
|
94
|
-
return '
|
|
194
|
+
return 'email_';
|
|
95
195
|
}
|
|
96
196
|
}
|
|
97
197
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
This folder for you resources to inline
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
const yup = require('yup');
|
|
2
|
+
const YupValidator = require('./drivers/YupValidator');
|
|
3
|
+
const CustomValidator = require('./drivers/CustomValidator');
|
|
4
|
+
const Base = require('../../modules/Base');
|
|
5
|
+
|
|
6
|
+
class ValidateService extends Base {
|
|
7
|
+
constructor(app, validator) {
|
|
8
|
+
super(app);
|
|
9
|
+
this.validator = validator
|
|
10
|
+
? this.constructor.getDriverByValidatorBody(app, validator)
|
|
11
|
+
: null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
static drivers = {
|
|
15
|
+
YupValidator,
|
|
16
|
+
CustomValidator,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
static isValidatorExists(validator) {
|
|
20
|
+
if (!(validator instanceof Object)) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return Object.values(this.drivers).some(
|
|
25
|
+
(driver) => validator instanceof driver,
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
static getDriverByValidatorBody(app, body) {
|
|
30
|
+
if (this.isValidatorExists(body)) {
|
|
31
|
+
return body;
|
|
32
|
+
}
|
|
33
|
+
if (yup.isSchema(body)) {
|
|
34
|
+
const yupValidator = new YupValidator(app, body);
|
|
35
|
+
return yupValidator;
|
|
36
|
+
}
|
|
37
|
+
const customValidator = new CustomValidator(app, body);
|
|
38
|
+
return customValidator;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Filter middlewares by route path and select all parameters
|
|
43
|
+
*/
|
|
44
|
+
static filterRelatedParametersByRoute(middlewares, method, path) {
|
|
45
|
+
const middlewaresParams = middlewares
|
|
46
|
+
.filter(
|
|
47
|
+
(middleware) =>
|
|
48
|
+
middleware.method.toLowerCase() === method.toLowerCase() &&
|
|
49
|
+
middleware.fullPath.toLowerCase() === path.toLowerCase(),
|
|
50
|
+
)
|
|
51
|
+
?.map((middleware) => {
|
|
52
|
+
const instance = new middleware.MiddlewareFunction(
|
|
53
|
+
this.app,
|
|
54
|
+
middleware.params,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
return instance.relatedReqParameters;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return middlewaresParams;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Group all middleware(routes + controller) parameters
|
|
65
|
+
*/
|
|
66
|
+
static getMiddlewareParams(
|
|
67
|
+
controllerMiddlewares,
|
|
68
|
+
AllrouteMiddlewares,
|
|
69
|
+
options,
|
|
70
|
+
) {
|
|
71
|
+
const { method, path } = options;
|
|
72
|
+
const routeMiddlewaresParams = this.filterRelatedParametersByRoute(
|
|
73
|
+
AllrouteMiddlewares,
|
|
74
|
+
method,
|
|
75
|
+
path,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const controllerMiddlewaresParams = this.filterRelatedParametersByRoute(
|
|
79
|
+
controllerMiddlewares,
|
|
80
|
+
method,
|
|
81
|
+
path,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
request: [
|
|
86
|
+
...controllerMiddlewaresParams.map((x) => x.request),
|
|
87
|
+
...routeMiddlewaresParams.map((x) => x.request),
|
|
88
|
+
],
|
|
89
|
+
query: [
|
|
90
|
+
...controllerMiddlewaresParams.map((x) => x.query),
|
|
91
|
+
...routeMiddlewaresParams.map((x) => x.query),
|
|
92
|
+
],
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// eslint-disable-next-line class-methods-use-this
|
|
97
|
+
async validateSchema(req, validator, data) {
|
|
98
|
+
if (!validator) {
|
|
99
|
+
return {};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
await validator.validateFields(data, req);
|
|
103
|
+
|
|
104
|
+
return validator.castFields(data, req);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async validateArrayOfSchemas(req, validators, data) {
|
|
108
|
+
const result = [];
|
|
109
|
+
|
|
110
|
+
for (const validator of validators) {
|
|
111
|
+
const formatedValidator = this.constructor.getDriverByValidatorBody(
|
|
112
|
+
this.app,
|
|
113
|
+
validator,
|
|
114
|
+
);
|
|
115
|
+
result.push(this.validateSchema(req, formatedValidator, data));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return Promise.all(result);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Validate req data. For example req.body, req.query
|
|
123
|
+
*/
|
|
124
|
+
async validateReqData(req, options) {
|
|
125
|
+
const { selectedReqData, additionalMiddlewareFieldsData } = options;
|
|
126
|
+
const {
|
|
127
|
+
middlewaresInfo,
|
|
128
|
+
routeMiddlewaresReg,
|
|
129
|
+
options: routeOptions,
|
|
130
|
+
} = additionalMiddlewareFieldsData;
|
|
131
|
+
|
|
132
|
+
let validatedFields = await this.validateSchema(
|
|
133
|
+
req,
|
|
134
|
+
this.validator,
|
|
135
|
+
selectedReqData,
|
|
136
|
+
);
|
|
137
|
+
const additionalMiddlewareSchemas = this.constructor.getMiddlewareParams(
|
|
138
|
+
middlewaresInfo,
|
|
139
|
+
routeMiddlewaresReg,
|
|
140
|
+
routeOptions,
|
|
141
|
+
)[routeOptions.prefix];
|
|
142
|
+
|
|
143
|
+
if (additionalMiddlewareSchemas.length) {
|
|
144
|
+
const middlewareValidatedFields = await this.validateArrayOfSchemas(
|
|
145
|
+
req,
|
|
146
|
+
additionalMiddlewareSchemas,
|
|
147
|
+
selectedReqData,
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
validatedFields = Object.assign(
|
|
151
|
+
{},
|
|
152
|
+
validatedFields,
|
|
153
|
+
...middlewareValidatedFields,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return validatedFields;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports = ValidateService;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
const yup = require('yup');
|
|
2
|
+
const ValidateService = require('./ValidateService');
|
|
3
|
+
const YupValidator = require('./drivers/YupValidator');
|
|
4
|
+
const CustomValidator = require('./drivers/CustomValidator');
|
|
5
|
+
|
|
6
|
+
describe('validate service', () => {
|
|
7
|
+
describe('validateSchema funtion', () => {
|
|
8
|
+
const data = {
|
|
9
|
+
name: '1213123123',
|
|
10
|
+
};
|
|
11
|
+
const req = {};
|
|
12
|
+
|
|
13
|
+
it('returns an empty object if no validator is provided', async () => {
|
|
14
|
+
expect.assertions(1);
|
|
15
|
+
const result = await new ValidateService(
|
|
16
|
+
global.server.app,
|
|
17
|
+
new YupValidator(
|
|
18
|
+
global.server.app,
|
|
19
|
+
yup.object().shape({ name: '123' }),
|
|
20
|
+
),
|
|
21
|
+
).validateSchema(req, undefined, data);
|
|
22
|
+
expect(result).toStrictEqual({});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('calls validateFields and castFields if validator is provided', async () => {
|
|
26
|
+
expect.assertions(1);
|
|
27
|
+
const validator = new YupValidator(
|
|
28
|
+
global.server.app,
|
|
29
|
+
yup.object().shape({ name: yup.string() }),
|
|
30
|
+
);
|
|
31
|
+
const result = await new ValidateService(
|
|
32
|
+
global.server.app,
|
|
33
|
+
{},
|
|
34
|
+
).validateSchema(req, validator, data);
|
|
35
|
+
expect(result).toStrictEqual({
|
|
36
|
+
name: '1213123123',
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('isValidatorExists funtion', () => {
|
|
42
|
+
it('returns false for non-object input', () => {
|
|
43
|
+
expect.assertions(1);
|
|
44
|
+
const validator = 'not an object';
|
|
45
|
+
const result = ValidateService.isValidatorExists(validator);
|
|
46
|
+
expect(result).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('returns true if validator is an instance of one of the drivers', () => {
|
|
50
|
+
expect.assertions(1);
|
|
51
|
+
const validator = new ValidateService.drivers.YupValidator();
|
|
52
|
+
const result = ValidateService.isValidatorExists(validator);
|
|
53
|
+
expect(result).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('returns false if validator is not an instance of any of the drivers', () => {
|
|
57
|
+
expect.assertions(1);
|
|
58
|
+
const validator = {};
|
|
59
|
+
const result = ValidateService.isValidatorExists(validator);
|
|
60
|
+
expect(result).toBe(false);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('getDriverByValidatorBody', () => {
|
|
65
|
+
it('should return the body if it is already a validator', () => {
|
|
66
|
+
expect.assertions(1);
|
|
67
|
+
const body = new YupValidator(
|
|
68
|
+
global.server.app,
|
|
69
|
+
yup.object().shape({ name: yup.string() }),
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const validator = ValidateService.getDriverByValidatorBody(
|
|
73
|
+
global.server.app,
|
|
74
|
+
body,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
expect(validator).toStrictEqual(body);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should return a YupValidator instance if the body is a Yup schema', () => {
|
|
81
|
+
expect.assertions(1);
|
|
82
|
+
const body = yup.object().shape({
|
|
83
|
+
name: '1234',
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const validator = ValidateService.getDriverByValidatorBody(
|
|
87
|
+
global.server.app,
|
|
88
|
+
body,
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
expect(validator).toBeInstanceOf(YupValidator);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should return CustomValidator if the body is neither a validator nor a Yup schema', () => {
|
|
95
|
+
expect.assertions(1);
|
|
96
|
+
const body = 'string';
|
|
97
|
+
const validator = ValidateService.getDriverByValidatorBody(
|
|
98
|
+
global.server.app,
|
|
99
|
+
body,
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
expect(validator).toBeInstanceOf(CustomValidator);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const Base = require('../../../modules/Base');
|
|
2
|
+
|
|
3
|
+
class AbstractValidator extends Base {
|
|
4
|
+
constructor(app, body) {
|
|
5
|
+
super(app);
|
|
6
|
+
this.body = body;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// eslint-disable-next-line no-unused-vars
|
|
10
|
+
static convertFieldsToJson(fields) {
|
|
11
|
+
// IMPLENT;
|
|
12
|
+
return {};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// eslint-disable-next-line class-methods-use-this
|
|
16
|
+
get fieldsInJsonFormat() {
|
|
17
|
+
// IMPLENT;
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// eslint-disable-next-line class-methods-use-this
|
|
22
|
+
async validateFields() {
|
|
23
|
+
// IMPLENT;
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// eslint-disable-next-line class-methods-use-this
|
|
28
|
+
async castFields() {
|
|
29
|
+
// IMPLENT;
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static get loggerGroup() {
|
|
34
|
+
return 'AbstractValidator_';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
module.exports = AbstractValidator;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const yup = require('yup');
|
|
2
|
+
|
|
3
|
+
const AbstractValidator = require('./AbstractValidator');
|
|
4
|
+
|
|
5
|
+
class CustomValidator extends AbstractValidator {
|
|
6
|
+
async validateFields(data, { query, body, appInfo }) {
|
|
7
|
+
if (this.body) {
|
|
8
|
+
if (typeof this.body.validate !== 'function') {
|
|
9
|
+
this.logger.error('request.validate should be a function');
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
await this.body.validate(data, {
|
|
14
|
+
req: {
|
|
15
|
+
query,
|
|
16
|
+
body,
|
|
17
|
+
appInfo,
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
} catch (e) {
|
|
21
|
+
this.logger.warn(`CustomValidator validateFields ${e}`);
|
|
22
|
+
if (e.path) {
|
|
23
|
+
throw new yup.ValidationError({
|
|
24
|
+
[e.path]: e.message,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
throw new Error(e);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async castFields(data, { query, body, appInfo }) {
|
|
32
|
+
if (this.body) {
|
|
33
|
+
if (typeof this.body.cast !== 'function') {
|
|
34
|
+
this.logger.error('request.validate should be a function');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return this.body.cast(data, {
|
|
39
|
+
req: {
|
|
40
|
+
query,
|
|
41
|
+
body,
|
|
42
|
+
appInfo,
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
static get loggerGroup() {
|
|
48
|
+
return 'CustomValidator_';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = CustomValidator;
|