@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.
Files changed (52) hide show
  1. package/CHANGELOG.md +30 -0
  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 +38 -27
  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 +18 -14
  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 +141 -41
  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 +13 -5
  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
@@ -11,7 +11,7 @@ declare class Cache extends Base {
11
11
  * Function return new key with added namespace
12
12
  * @param key key to add namespace
13
13
  */
14
- getKeyWithNameSpace(key: String): String;
14
+ getKeyWithNameSpace(key: string): string;
15
15
 
16
16
  /**
17
17
  * Get value from cache. Set and get if not eists
@@ -20,7 +20,7 @@ declare class Cache extends Base {
20
20
  * @param storeTime how long we should store value on cache
21
21
  */
22
22
  getSetValue(
23
- key: String,
23
+ key: string,
24
24
  onNotFound: () => Promise<any>,
25
25
  storeTime: number,
26
26
  ): Promise<any>;
@@ -29,7 +29,7 @@ declare class Cache extends Base {
29
29
  * Remove key from cache
30
30
  * @param key key to remove
31
31
  */
32
- removeKey(key: String): Promise<number>;
32
+ removeKey(key: string): Promise<number>;
33
33
  }
34
34
 
35
35
  export = Cache;
@@ -66,13 +66,27 @@ class Cache extends Base {
66
66
  return Promise.reject(e);
67
67
  }
68
68
 
69
- this.redisClient.setEx(key, storeTime, JSON.stringify(result));
69
+ this.redisClient.setEx(
70
+ key,
71
+ storeTime,
72
+ JSON.stringify(result, (jsonkey, value) =>
73
+ typeof value === 'bigint' ? `${value}n` : value,
74
+ ),
75
+ );
70
76
  } else {
71
77
  this.logger.verbose(
72
- `getSetValueFromCache FROM CACHE key ${key}, value ${result}`,
78
+ `getSetValueFromCache FROM CACHE key ${key}, value ${result.substring(
79
+ 0,
80
+ 100,
81
+ )}`,
73
82
  );
74
83
  try {
75
- result = JSON.parse(result);
84
+ result = JSON.parse(result, (jsonkey, value) => {
85
+ if (typeof value === 'string' && /^\d+n$/.test(value)) {
86
+ return BigInt(value.slice(0, value.length - 1));
87
+ }
88
+ return value;
89
+ });
76
90
  } catch (e) {
77
91
  this.logger.warn(
78
92
  'Not able to parse json from redis cache. That can be a normal in case you store string here',
@@ -0,0 +1,171 @@
1
+ const Base = require('../../modules/Base');
2
+ const ValidateService = require('../validate/ValidateService');
3
+
4
+ class DocumentationGenerator extends Base {
5
+ static processingFields(fieldsByRoute) {
6
+ const fields = [];
7
+ if (!fieldsByRoute) {
8
+ return fields;
9
+ }
10
+ const entries = Object.entries(fieldsByRoute);
11
+ entries.forEach(([key, value]) => {
12
+ const field = {};
13
+ field.name = key;
14
+ field.type = value.type;
15
+ if (value.exclusiveTests) {
16
+ field.required = value.exclusiveTests.required;
17
+ }
18
+ if (value?.innerType) {
19
+ field.innerType = value?.innerType?.type;
20
+ }
21
+
22
+ if (value.fields) {
23
+ field.fields = [];
24
+ // eslint-disable-next-line no-shadow
25
+ const entries = Object.entries(value.fields);
26
+ // eslint-disable-next-line no-shadow
27
+ entries.forEach(([key, value]) => {
28
+ field.fields.push({
29
+ name: key,
30
+ type: value.type,
31
+ });
32
+ });
33
+ }
34
+ fields.push(field);
35
+ });
36
+ return fields;
37
+ }
38
+
39
+ static selectUniqueFields(fields) {
40
+ return Array.from(
41
+ new Map(fields.map((item) => [item.name, item])).values(),
42
+ ).reduce((uniqueArray, item) => {
43
+ const existingItem = uniqueArray.find(
44
+ (uniqueItem) => uniqueItem.name === item.name,
45
+ );
46
+ if (!existingItem) {
47
+ uniqueArray.push(item);
48
+ } else if (item.required) {
49
+ existingItem.required = true;
50
+ }
51
+ return uniqueArray;
52
+ }, []);
53
+ }
54
+
55
+ static groupFieldsFromSchemas(schemas) {
56
+ const result = [];
57
+ schemas.forEach((schema) => {
58
+ const convertedSchema = new ValidateService(this.app, schema).validator;
59
+
60
+ for (const [key, value] of Object.entries(
61
+ convertedSchema?.fieldsInJsonFormat,
62
+ )) {
63
+ result.push({
64
+ name: key,
65
+ type: value.type,
66
+ required: value.required,
67
+ });
68
+ }
69
+ });
70
+
71
+ return result;
72
+ }
73
+
74
+ static convertDataToDocumentationElement(
75
+ controllerName,
76
+ routesInfo,
77
+ middlewaresInfo,
78
+ routeMiddlewaresReg,
79
+ ) {
80
+ return {
81
+ contollerName: controllerName,
82
+ routesInfo: routesInfo.map((route) => {
83
+ const middlewareQueryParams = ValidateService.getMiddlewareParams(
84
+ middlewaresInfo,
85
+ routeMiddlewaresReg,
86
+ {
87
+ method: route.method.toLowerCase(),
88
+ path: route.fullPath,
89
+ },
90
+ ).query;
91
+
92
+ const middlewareRequestParams = ValidateService.getMiddlewareParams(
93
+ middlewaresInfo,
94
+ routeMiddlewaresReg,
95
+ {
96
+ method: route.method.toLowerCase(),
97
+ path: route.fullPath,
98
+ },
99
+ ).request;
100
+
101
+ const queryParams = this.groupFieldsFromSchemas(middlewareQueryParams);
102
+
103
+ const requestParams = this.groupFieldsFromSchemas(
104
+ middlewareRequestParams,
105
+ );
106
+
107
+ return {
108
+ [route.fullPath]: {
109
+ method: route.method,
110
+ name: route.name,
111
+ description: route?.description,
112
+ fields: this.selectUniqueFields([
113
+ ...this.processingFields(route.fields),
114
+ ...requestParams,
115
+ ]),
116
+ queryFields: this.selectUniqueFields([
117
+ ...this.processingFields(route.queryFields),
118
+ ...queryParams,
119
+ ]),
120
+ routeMiddlewares: routeMiddlewaresReg
121
+ .map((middleware) => {
122
+ const routeFullPath = route.fullPath.toUpperCase();
123
+ const middlewareFullPath = middleware.fullPath.toUpperCase();
124
+ if (
125
+ route.method.toLowerCase() ===
126
+ middleware.method.toLowerCase() &&
127
+ (middlewareFullPath === routeFullPath ||
128
+ middlewareFullPath === `${routeFullPath}*`)
129
+ ) {
130
+ return {
131
+ name: middleware.name,
132
+ params: middleware.params,
133
+ authParams: middleware.authParams,
134
+ };
135
+ }
136
+ return null;
137
+ })
138
+ .filter(Boolean),
139
+ controllerMiddlewares: [
140
+ ...new Set(
141
+ middlewaresInfo
142
+ .filter((middleware) => {
143
+ const routeFullPath = route.fullPath.toUpperCase();
144
+ const middlewareFullPath =
145
+ middleware.fullPath.toUpperCase();
146
+ const middlewareFullPathWithSliced = middleware.fullPath
147
+ .toUpperCase()
148
+ .slice(0, -1);
149
+
150
+ return (
151
+ middlewareFullPath === routeFullPath ||
152
+ middlewareFullPath === `${routeFullPath}*` ||
153
+ routeFullPath?.indexOf(middlewareFullPathWithSliced) !==
154
+ -1
155
+ );
156
+ })
157
+ .map(({ name, params, authParams }) => ({
158
+ name,
159
+ params,
160
+ authParams,
161
+ })),
162
+ ),
163
+ ],
164
+ },
165
+ };
166
+ }),
167
+ };
168
+ }
169
+ }
170
+
171
+ module.exports = DocumentationGenerator;
@@ -1,13 +1,12 @@
1
+ const http = require('node:http');
2
+ const path = require('node:path');
1
3
  const express = require('express');
2
- const http = require('http');
3
- const path = require('path');
4
4
  const cors = require('cors');
5
5
 
6
- const i18next = require('i18next');
7
- const i18nextMiddleware = require('i18next-http-middleware');
8
- const BackendFS = require('i18next-fs-backend');
9
- const Backend = require('i18next-chained-backend');
6
+ const RequestLoggerMiddleware = require('./middleware/RequestLogger');
7
+ const I18nMiddleware = require('./middleware/I18n');
10
8
  const PrepareAppInfoMiddleware = require('./middleware/PrepareAppInfo');
9
+ const RequestParserMiddleware = require('./middleware/RequestParser');
11
10
 
12
11
  const Base = require('../../modules/Base');
13
12
 
@@ -15,25 +14,19 @@ const Base = require('../../modules/Base');
15
14
  * HTTP server based on Express
16
15
  */
17
16
  class HttpServer extends Base {
18
- constructor(app, folderConfig) {
17
+ constructor(app) {
19
18
  super(app);
20
19
  this.express = express();
20
+ this.express.disable('x-powered-by');
21
21
  this.express.set('views', [
22
- folderConfig.folders.views,
22
+ this.app.foldersConfig.views,
23
23
  path.join(__dirname, '../../views'),
24
24
  ]);
25
25
  this.express.set('view engine', 'pug');
26
- this.express.use((req, res, next) => {
27
- const startTime = Date.now();
28
- const text = `Request is [${req.method}] ${req.url}`;
29
- this.logger.info(text);
30
- res.on('finish', () => {
31
- const duration = Date.now() - startTime;
32
- this.logger.info(`Finished ${text}. Duration ${duration} ms`);
33
- });
34
- next();
35
- });
36
- this.enableI18N(folderConfig);
26
+
27
+ this.express.use(new PrepareAppInfoMiddleware(this.app).getMiddleware());
28
+ this.express.use(new RequestLoggerMiddleware(this.app).getMiddleware());
29
+ this.express.use(new I18nMiddleware(this.app).getMiddleware());
37
30
 
38
31
  const httpConfig = this.app.getConfig('http');
39
32
  this.express.use(
@@ -41,17 +34,16 @@ class HttpServer extends Base {
41
34
  origin: httpConfig.corsDomains,
42
35
  }),
43
36
  ); // todo whitelist
44
- this.express.use(express.urlencoded({ limit: '50mb', extended: true }));
45
- this.express.use(express.json({ limit: '50mb' }));
46
- this.express.use(express.static(folderConfig.folders.public));
37
+ this.express.use(express.static(this.app.foldersConfig.public));
47
38
  this.express.use(express.static('./public'));
48
39
 
49
- this.express.use(new PrepareAppInfoMiddleware(this.app).getMiddleware());
40
+ this.express.use(new RequestParserMiddleware(this.app).getMiddleware());
50
41
 
51
42
  // As exprress will check numbersof arguments
52
43
  // eslint-disable-next-line no-unused-vars
53
44
  this.express.use((err, req, res, next) => {
54
45
  // error handling
46
+ // eslint-disable-next-line no-console
55
47
  console.error(err.stack);
56
48
  // TODO
57
49
  res.status(500).send('Something broke!');
@@ -79,85 +71,13 @@ class HttpServer extends Base {
79
71
  );
80
72
  }
81
73
 
82
- /**
83
- * Enable support for i18n
84
- * @param {object} folderConfig config
85
- * @param {object} folderConfig.folders folder config
86
- * @param {string} folderConfig.folders.config path to folder with config files
87
- * @param {string} folderConfig.folders.models path to folder with moidels files
88
- * @param {string} folderConfig.folders.controllers path to folder with controllers files
89
- * @param {string} folderConfig.folders.views path to folder with view files
90
- * @param {string} folderConfig.folders.public path to folder with public files
91
- * @param {string} folderConfig.folders.locales path to folder with locales files
92
- * @param {string} folderConfig.folders.emails path to folder with emails files
93
- */
94
- enableI18N(folderConfig) {
95
- const I18NConfig = this.app.getConfig('i18n');
96
- if (!I18NConfig.enabled) {
97
- return;
98
- }
99
- const lngDetector = new i18nextMiddleware.LanguageDetector();
100
- lngDetector.addDetector({
101
- name: 'xLang',
102
- // eslint-disable-next-line no-unused-vars
103
- lookup: (req, res, options) => {
104
- const lng = req.get('X-Lang');
105
- if (lng) {
106
- return lng;
107
- }
108
- return false;
109
- },
110
- // eslint-disable-next-line no-unused-vars
111
- cacheUserLanguage: (req, res, lng, options) => {},
112
- });
113
- this.logger.info('Enabling i18n support');
114
- i18next
115
- .use(Backend)
116
- .use(lngDetector)
117
- .init({
118
- backend: {
119
- backends: [
120
- BackendFS,
121
- // BackendFS,
122
- ],
123
- backendOptions: [
124
- // {
125
- // loadPath: __dirname + '/../../locales/{{lng}}/{{ns}}.json',
126
- // addPath: __dirname + '/../../locales/{{lng}}/{{ns}}.missing.json'
127
- // },
128
- {
129
- loadPath: `${folderConfig.folders.locales}/{{lng}}/{{ns}}.json`,
130
- addPath: `${folderConfig.folders.locales}/{{lng}}/{{ns}}.missing.json`,
131
- },
132
- ],
133
- },
134
- fallbackLng: I18NConfig.fallbackLng,
135
- preload: I18NConfig.preload,
136
- saveMissing: I18NConfig.saveMissing,
137
- debug: I18NConfig.debug,
138
- detection: {
139
- // caches: ['cookie'],
140
- order: I18NConfig.langDetectionOders || ['xLang'],
141
- lookupQuerystring: I18NConfig.lookupQuerystring,
142
- },
143
- });
144
- this.express.use(i18nextMiddleware.handle(i18next));
145
- this.express.use((req, res, next) => {
146
- // f ix ru-Ru, en-US, etc
147
- if (res.locals.language.length !== 2) {
148
- [res.locals.language] = res.locals.language.split('-');
149
- }
150
- next();
151
- });
152
- }
153
-
154
74
  /**
155
75
  * Add handle for 404 error
156
76
  */
157
77
  add404Page() {
158
78
  this.express.use((req, res) => {
159
79
  // error handling
160
- res.status(404).render('404');
80
+ res.status(404).json({ message: '404' });
161
81
  });
162
82
  }
163
83
 
@@ -1,3 +1,4 @@
1
+ const yup = require('yup');
1
2
  const Base = require('../../../modules/Base');
2
3
 
3
4
  class AbstractMiddleware extends Base {
@@ -14,6 +15,25 @@ class AbstractMiddleware extends Base {
14
15
  return [];
15
16
  }
16
17
 
18
+ // eslint-disable-next-line class-methods-use-this
19
+ get relatedQueryParameters() {
20
+ // For example yup.object().shape({page: yup.number().required(),limit: yup.number()})
21
+ return yup.object().shape({});
22
+ }
23
+
24
+ // eslint-disable-next-line class-methods-use-this
25
+ get relatedRequestParameters() {
26
+ // For example yup.object().shape({page: yup.number().required(),limit: yup.number()})
27
+ return yup.object().shape({});
28
+ }
29
+
30
+ get relatedReqParameters() {
31
+ return {
32
+ request: this.relatedRequestParameters,
33
+ query: this.relatedQueryParameters,
34
+ };
35
+ }
36
+
17
37
  async middleware(req, res, next) {
18
38
  this.logger.warn('Middleware is not implemented');
19
39
  next();
@@ -17,6 +17,10 @@ class GetUserByToken extends AbstractMiddleware {
17
17
  }
18
18
 
19
19
  async middleware(req, res, next) {
20
+ if (req.appInfo.user) {
21
+ this.logger.warn('You call GetUserByToken more then once');
22
+ return next();
23
+ }
20
24
  let { token } = req.body;
21
25
  this.logger.verbose(
22
26
  `GetUserByToken token in BODY ${token}. Token if Authorization header ${req.get(
@@ -0,0 +1,119 @@
1
+ const i18next = require('i18next');
2
+ const BackendFS = require('i18next-fs-backend');
3
+ const Backend = require('i18next-chained-backend');
4
+
5
+ const AbstractMiddleware = require('./AbstractMiddleware');
6
+
7
+ class I18n extends AbstractMiddleware {
8
+ constructor(app, params) {
9
+ super(app, params);
10
+ const I18NConfig = this.app.getConfig('i18n');
11
+ this.i18n = {
12
+ t: (text) => text,
13
+ language: I18NConfig.fallbackLng,
14
+ };
15
+ this.cache = {};
16
+
17
+ if (I18NConfig.enabled) {
18
+ this.logger.info('Enabling i18n support');
19
+ this.i18n = i18next;
20
+ i18next.use(Backend).init({
21
+ backend: {
22
+ backends: [
23
+ BackendFS,
24
+ // BackendFS,
25
+ ],
26
+ backendOptions: [
27
+ // {
28
+ // loadPath: __dirname + '/../../locales/{{lng}}/{{ns}}.json',
29
+ // addPath: __dirname + '/../../locales/{{lng}}/{{ns}}.missing.json'
30
+ // },
31
+ {
32
+ loadPath: `${this.app.foldersConfig.locales}/{{lng}}/{{ns}}.json`,
33
+ addPath: `${this.app.foldersConfig.locales}/{{lng}}/{{ns}}.missing.json`,
34
+ },
35
+ ],
36
+ },
37
+ fallbackLng: I18NConfig.fallbackLng,
38
+ preload: I18NConfig.preload,
39
+ saveMissing: I18NConfig.saveMissing,
40
+ debug: I18NConfig.debug,
41
+ });
42
+ }
43
+
44
+ this.enabled = I18NConfig.enabled;
45
+ this.lookupQuerystring = I18NConfig.lookupQuerystring;
46
+ this.supportedLngs = I18NConfig.supportedLngs;
47
+ this.fallbackLng = I18NConfig.fallbackLng;
48
+ }
49
+
50
+ static get description() {
51
+ return 'Provide language detection and translation';
52
+ }
53
+
54
+ async middleware(req, res, next) {
55
+ let i18n;
56
+
57
+ if (this.enabled) {
58
+ let lang = this.detectLang(req);
59
+ if (!lang || this.supportedLngs.indexOf(lang) === -1) {
60
+ this.logger.verbose(
61
+ `Language "${lang}" is not supported or not detected. Using fallback on ${this.fallbackLng}`,
62
+ );
63
+ lang = this.fallbackLng;
64
+ }
65
+
66
+ if (!this.cache[lang]) {
67
+ this.cache[lang] = i18next.cloneInstance({
68
+ initImmediate: false,
69
+ lng: lang,
70
+ });
71
+ }
72
+ i18n = this.cache[lang];
73
+ }
74
+
75
+ if (!i18n) {
76
+ i18n = this.i18n;
77
+ }
78
+
79
+ req.appInfo.i18n = i18n;
80
+ req.i18n = new Proxy(req.appInfo.i18n, {
81
+ get: (target, prop) => {
82
+ this.logger.warn('Please not use "req.i18n" Use "req.appInfo.i18n"');
83
+ return target[prop];
84
+ },
85
+ });
86
+
87
+ return next();
88
+ }
89
+
90
+ detectors = {
91
+ XLang: (req) => req.get('X-Lang'), // grab from header
92
+ query: (req) => (req.query ? req.query[this.lookupQuerystring] : false), // grab from query
93
+ user: (req) => req.appInfo?.user?.locale, // what if we have a user and user have a defined locale?
94
+ };
95
+
96
+ detectorOrder = ['XLang', 'query', 'user'];
97
+
98
+ detectLang(req, isUseShortCode = true) {
99
+ let lang = '';
100
+ for (const detectorName of this.detectorOrder) {
101
+ const lng = this.detectors[detectorName](req);
102
+ if (!lng) {
103
+ // eslint-disable-next-line no-continue
104
+ continue;
105
+ }
106
+ if (i18next.services.languageUtils.isSupportedCode(lng)) {
107
+ if (isUseShortCode) {
108
+ lang = i18next.services.languageUtils.getLanguagePartFromCode(lng);
109
+ } else {
110
+ lang = lng;
111
+ }
112
+ break;
113
+ }
114
+ }
115
+ return lang;
116
+ }
117
+ }
118
+
119
+ module.exports = I18n;
@@ -0,0 +1,77 @@
1
+ const I18n = require('./I18n');
2
+
3
+ describe('i18n middleware methods', () => {
4
+ let middleware;
5
+ beforeAll(() => {
6
+ middleware = new I18n(global.server.app);
7
+ });
8
+ it('have description fields', async () => {
9
+ expect.assertions(1);
10
+ expect(middleware.constructor.description).toBeDefined();
11
+ });
12
+
13
+ it('detectors should works correctly', async () => {
14
+ expect.assertions(5);
15
+ const request = {
16
+ get: () => 'en',
17
+ query: {
18
+ [middleware.lookupQuerystring]: 'es',
19
+ },
20
+ };
21
+ let lang = await middleware.detectLang(request);
22
+ expect(lang).toBe('en');
23
+
24
+ request.appInfo = {
25
+ user: {
26
+ locale: 'be',
27
+ },
28
+ };
29
+ lang = await middleware.detectLang(request);
30
+ expect(lang).toBe('en');
31
+ request.get = () => null;
32
+ lang = await middleware.detectLang(request);
33
+ expect(lang).toBe('es');
34
+
35
+ delete request.query;
36
+ lang = await middleware.detectLang(request);
37
+ expect(lang).toBe('be');
38
+
39
+ request.query = {
40
+ [middleware.lookupQuerystring]: 'en-GB',
41
+ };
42
+ lang = await middleware.detectLang(request);
43
+ expect(lang).toBe('en');
44
+ });
45
+
46
+ it('middleware that works', async () => {
47
+ expect.assertions(4);
48
+ const nextFunction = jest.fn(() => {});
49
+ const req = {
50
+ get: () => 'en',
51
+ appInfo: {},
52
+ };
53
+ await middleware.middleware(req, {}, nextFunction);
54
+ expect(nextFunction).toHaveBeenCalledWith();
55
+ expect(req.appInfo.i18n).toBeDefined();
56
+ expect(req.appInfo.i18n.t('aaaaa')).toBe('aaaaa');
57
+ expect(req.i18n.t('aaaaa')).toBe('aaaaa'); // proxy test
58
+ });
59
+
60
+ it('middleware disabled', async () => {
61
+ expect.assertions(4);
62
+ global.server.app.updateConfig('i18n', { enabled: false });
63
+ middleware = new I18n(global.server.app);
64
+
65
+ const nextFunction = jest.fn(() => {});
66
+ const req = {
67
+ get: () => 'en',
68
+ appInfo: {},
69
+ };
70
+ await middleware.middleware(req, {}, nextFunction);
71
+ expect(nextFunction).toHaveBeenCalledWith();
72
+ expect(req.appInfo.i18n).toBeDefined();
73
+ expect(req.appInfo.i18n.t('aaaaa')).toBe('aaaaa');
74
+ expect(req.i18n.t('aaaaa')).toBe('aaaaa'); // proxy test
75
+ global.server.app.updateConfig('i18n', { enabled: true });
76
+ });
77
+ });
@@ -0,0 +1,56 @@
1
+ const yup = require('yup');
2
+ const AbstractMiddleware = require('./AbstractMiddleware');
3
+ /**
4
+ * Middleware for reusing pagination
5
+ */
6
+ class Pagination extends AbstractMiddleware {
7
+ static get description() {
8
+ return 'Pagination middleware. You can use limit=10 and maxLimit=100 parameters';
9
+ }
10
+
11
+ // eslint-disable-next-line class-methods-use-this
12
+ get relatedQueryParameters() {
13
+ return yup.object().shape({
14
+ page: yup.number(),
15
+ limit: yup.number(),
16
+ });
17
+ }
18
+
19
+ async middleware(req, res, next) {
20
+ let { limit, maxLimit } = this.params;
21
+
22
+ limit = typeof limit === 'number' ? parseInt(limit, 10) : 10;
23
+ maxLimit = typeof maxLimit === 'number' ? parseInt(maxLimit, 10) : 100;
24
+
25
+ req.appInfo.pagination = {};
26
+ req.appInfo.pagination.page =
27
+ typeof req?.query?.page === 'string'
28
+ ? parseInt(req?.query?.page, 10) || 1
29
+ : 1;
30
+
31
+ req.appInfo.pagination.limit =
32
+ typeof req?.query?.limit === 'string'
33
+ ? parseInt(req?.query?.limit, 10) || 0
34
+ : limit;
35
+
36
+ if (req.appInfo.pagination.limit > maxLimit) {
37
+ req.appInfo.pagination.limit = maxLimit;
38
+ }
39
+
40
+ if (req.appInfo.pagination.page < 1) {
41
+ req.appInfo.pagination.page = 1;
42
+ }
43
+
44
+ if (req.appInfo.pagination.limit < 0) {
45
+ req.appInfo.pagination.limit = 0;
46
+ }
47
+
48
+ req.appInfo.pagination.skip =
49
+ req.appInfo.pagination.page * req.appInfo.pagination.limit -
50
+ req.appInfo.pagination.limit;
51
+
52
+ return next();
53
+ }
54
+ }
55
+
56
+ module.exports = Pagination;