@adaptivestone/framework 3.4.3 → 4.1.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +37 -3
  2. package/LICENCE +21 -0
  3. package/cluster.js +3 -3
  4. package/commands/CreateUser.js +27 -0
  5. package/commands/Documentation.js +1 -1
  6. package/commands/GetOpenApiJson.js +53 -23
  7. package/commands/migration/Create.js +2 -2
  8. package/config/auth.js +1 -1
  9. package/config/i18n.js +4 -3
  10. package/config/mail.js +5 -1
  11. package/controllers/Home.js +2 -2
  12. package/controllers/Home.test.js +11 -0
  13. package/controllers/index.js +15 -15
  14. package/folderConfig.js +1 -1
  15. package/helpers/yup.js +24 -0
  16. package/index.js +8 -0
  17. package/models/User.js +40 -30
  18. package/models/User.test.js +68 -18
  19. package/modules/AbstractController.js +144 -208
  20. package/modules/AbstractModel.js +2 -1
  21. package/modules/Base.js +3 -2
  22. package/modules/BaseCli.js +6 -2
  23. package/package.json +20 -16
  24. package/server.d.ts +1 -1
  25. package/server.js +25 -8
  26. package/services/cache/Cache.d.ts +3 -3
  27. package/services/cache/Cache.js +17 -3
  28. package/services/documentation/DocumentationGenerator.js +171 -0
  29. package/services/http/HttpServer.js +16 -96
  30. package/services/http/middleware/AbstractMiddleware.js +20 -0
  31. package/services/http/middleware/GetUserByToken.js +4 -0
  32. package/services/http/middleware/I18n.js +119 -0
  33. package/services/http/middleware/I18n.test.js +77 -0
  34. package/services/http/middleware/Pagination.js +56 -0
  35. package/services/http/middleware/PrepareAppInfo.test.js +22 -0
  36. package/services/http/middleware/{Middlewares.test.js → RateLimiter.test.js} +1 -1
  37. package/services/http/middleware/RequestLogger.js +22 -0
  38. package/services/http/middleware/RequestParser.js +36 -0
  39. package/services/messaging/email/index.js +162 -42
  40. package/services/messaging/email/resources/.gitkeep +1 -0
  41. package/services/validate/ValidateService.js +161 -0
  42. package/services/validate/ValidateService.test.js +105 -0
  43. package/services/validate/drivers/AbstractValidator.js +37 -0
  44. package/services/validate/drivers/CustomValidator.js +52 -0
  45. package/services/validate/drivers/YupValidator.js +103 -0
  46. package/tests/setup.js +2 -0
  47. package/services/messaging/email/templates/emptyTemplate/style.less +0 -0
  48. package/services/messaging/email/templates/password/html.handlebars +0 -13
  49. package/services/messaging/email/templates/password/style.less +0 -0
  50. package/services/messaging/email/templates/password/subject.handlebars +0 -1
  51. package/services/messaging/email/templates/password/text.handlebars +0 -1
  52. package/services/messaging/email/templates/verification/style.less +0 -0
@@ -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;
@@ -0,0 +1,103 @@
1
+ const yup = require('yup');
2
+ const AbstractValidator = require('./AbstractValidator');
3
+
4
+ class YupValidator extends AbstractValidator {
5
+ get fieldsInJsonFormat() {
6
+ return this.constructor.convertFieldsToJson(this.body);
7
+ }
8
+
9
+ static convertFieldsToJson(fields) {
10
+ const convertedFields = {};
11
+ const entries = Object.entries(fields.describe().fields);
12
+
13
+ if (!entries?.length) {
14
+ return convertedFields;
15
+ }
16
+ const requiredFields = [];
17
+
18
+ for (const [field, fieldProp] of entries) {
19
+ const isRequired = fieldProp?.tests?.find(
20
+ (prop) => prop.name === 'required',
21
+ );
22
+ if (isRequired) {
23
+ requiredFields.push(field);
24
+ }
25
+ }
26
+
27
+ entries.forEach(([key, value]) => {
28
+ if (!convertedFields[key]) {
29
+ convertedFields[key] = {};
30
+ }
31
+
32
+ convertedFields[key] = {
33
+ type: value.type,
34
+ required: requiredFields?.includes(key),
35
+ };
36
+ });
37
+
38
+ return convertedFields;
39
+ }
40
+
41
+ async validateFields(data, { query, body, appInfo }) {
42
+ const yupSchema = this.body;
43
+ const { controllerValidationAbortEarly } = this.app.getConfig('validate');
44
+ if (yupSchema) {
45
+ if (typeof yupSchema.validate !== 'function') {
46
+ this.logger.error('request.validate should be a function');
47
+ }
48
+ }
49
+
50
+ try {
51
+ await yupSchema.validate(data, {
52
+ abortEarly: controllerValidationAbortEarly,
53
+ req: { query, body },
54
+ });
55
+ } catch (e) {
56
+ let { errors } = e;
57
+ // translate it
58
+ if (appInfo.i18n && errors) {
59
+ errors = errors.map((err) => appInfo.i18n.t(err, err));
60
+ }
61
+ this.logger.error(
62
+ `Request validation failed with message: ${e.message}. errors: ${errors}`,
63
+ );
64
+
65
+ const errorAnswer = {};
66
+ if (!e.inner || !e.inner.length) {
67
+ errorAnswer[e.path] = errors;
68
+ } else {
69
+ e.inner.forEach((err) => {
70
+ errorAnswer[err.path] = err.errors;
71
+ if (appInfo.i18n && err.errors) {
72
+ errorAnswer[err.path] = err.errors.map((err1) =>
73
+ appInfo.i18n.t(err1, err1, err.params),
74
+ );
75
+ }
76
+ });
77
+ }
78
+
79
+ throw new yup.ValidationError({
80
+ ...errorAnswer,
81
+ });
82
+ }
83
+ }
84
+
85
+ async castFields(data, { query, body }) {
86
+ const yupSchema = this.body;
87
+ if (yupSchema) {
88
+ if (typeof yupSchema.cast !== 'function') {
89
+ this.logger.error('request.cast should be a function');
90
+ }
91
+ }
92
+
93
+ return yupSchema.cast(data, {
94
+ stripUnknown: true,
95
+ req: { query, body },
96
+ });
97
+ }
98
+
99
+ static get loggerGroup() {
100
+ return 'YupValidator_';
101
+ }
102
+ }
103
+ module.exports = YupValidator;
package/tests/setup.js CHANGED
@@ -56,7 +56,9 @@ beforeAll(async () => {
56
56
  nick: 'testUserNickName',
57
57
  },
58
58
  }).catch((e) => {
59
+ // eslint-disable-next-line no-console
59
60
  console.error(e);
61
+ // eslint-disable-next-line no-console
60
62
  console.info(
61
63
  'That error can happens in case you have custom user model. Please use global.testSetup.disableUserCreate flag to skip user creating',
62
64
  );
@@ -1,13 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <title>{{t('email.newPassword')}}</title>
6
- </head>
7
- <body>
8
- <h1>{{t('email.greeating')}} {{username}}</h1>
9
- <p>Ваш новый пароль <b>{{password}}</b> <br>
10
- не забудьте его поменять в личном кабинете. <br>
11
- <a href="http://{{realm}}.localhost:3000/#/login">Войти на сайт</a></p>
12
- </body>
13
- </html>
@@ -1 +0,0 @@
1
- Новый пароль
@@ -1 +0,0 @@
1
- Добрый день {{username}}Для создание нового пароля перейдите по ссылке {{link}}