@vulkano/core 1.15.6 → 1.17.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.
@@ -2,13 +2,9 @@
2
2
  * Database connection
3
3
  */
4
4
 
5
- const Promise = require('bluebird');
6
- const paginate = require('mongoose-paginate-v2');
7
- const merge = require('deepmerge');
8
-
9
5
  const mongoose = require('mongoose');
10
-
11
- mongoose.Promise = Promise;
6
+ const paginate = require('mongoose-paginate-v2');
7
+ const merge = require('../libs/Merge');
12
8
 
13
9
  global.mongoose = mongoose;
14
10
  global.Virtual = 'Virtual';
@@ -45,15 +41,13 @@ module.exports = async function loadDatabaseApplication() {
45
41
  : (connection || null);
46
42
 
47
43
  if (!toConnect) {
48
- throw `Invalid conection to user MongoDB with source ${connection}`;
44
+ throw new Error(`Invalid connection to MongoDB with source "${connection}"`);
49
45
  }
50
46
 
51
- const defaultProps = {
52
- family: 4
53
- };
54
-
47
+ // Build connection props from user config; family defaults to 4 (IPv4)
48
+ // unless explicitly set to another value in database.config
55
49
  const connectionProps = merge.all([
56
- defaultProps,
50
+ { family: 4 },
57
51
  (database ? database.config || {} : {})
58
52
  ]);
59
53
 
@@ -80,13 +80,30 @@ module.exports = {
80
80
  */
81
81
  update(_id, data) {
82
82
 
83
+ // Blocklist: these fields are never writable from outside
84
+ const BLOCKED = ['_id', 'createdAt', '__v'];
85
+ const sanitized = { ...data };
86
+ BLOCKED.forEach((field) => delete sanitized[field]);
87
+
88
+ // Allowlist: if fillable is defined and non-empty, only those fields pass through
89
+ const { fillable } = this;
90
+ let filtered = sanitized;
91
+ if (Array.isArray(fillable) && fillable.length > 0) {
92
+ filtered = {};
93
+ fillable.forEach((field) => {
94
+ if (sanitized[field] !== undefined) {
95
+ filtered[field] = sanitized[field];
96
+ }
97
+ });
98
+ }
99
+
83
100
  return this.getByField(_id)
84
101
  .then( (record) => {
85
102
 
86
103
  // Merge current info with incoming values
87
104
  const merged = {
88
105
  ...record,
89
- ...data,
106
+ ...filtered,
90
107
  updatedAt: Date.now()
91
108
  };
92
109
 
@@ -110,10 +127,7 @@ module.exports = {
110
127
  */
111
128
  delete(id) {
112
129
 
113
- // Scaffold Model
114
- // const Model = global[modelName];
115
-
116
- // Soft delete
130
+ // Soft delete: set active=false instead of removing the document
117
131
  return this.update(id, { active: false });
118
132
 
119
133
  },
package/libs/ApiClient.js CHANGED
@@ -1,6 +1,4 @@
1
- const http = require('http');
2
- const https = require('https');
3
- const axios = require('axios');
1
+ const { Agent } = require('undici');
4
2
 
5
3
  module.exports = {
6
4
 
@@ -12,13 +10,7 @@ module.exports = {
12
10
  * @returns {Promise}
13
11
  */
14
12
  get(url, props) {
15
-
16
- return this.send({
17
- url,
18
- ...props,
19
- method: 'GET'
20
- });
21
-
13
+ return this.send({ url, ...props, method: 'GET' });
22
14
  },
23
15
 
24
16
  /**
@@ -30,14 +22,7 @@ module.exports = {
30
22
  * @returns {Promise}
31
23
  */
32
24
  post(url, body, props) {
33
-
34
- return this.send({
35
- url,
36
- body,
37
- ...props,
38
- method: 'POST'
39
- });
40
-
25
+ return this.send({ url, body, ...props, method: 'POST' });
41
26
  },
42
27
 
43
28
  /**
@@ -49,32 +34,18 @@ module.exports = {
49
34
  * @returns {Promise}
50
35
  */
51
36
  put(url, body, props) {
52
-
53
- return this.send({
54
- url,
55
- body,
56
- ...props,
57
- method: 'PUT'
58
- });
59
-
37
+ return this.send({ url, body, ...props, method: 'PUT' });
60
38
  },
61
39
 
62
40
  /**
63
41
  * Method to make a DELETE request
64
42
  *
65
43
  * @param {String} url
66
- * @param {Object} body
67
44
  * @param {Object} props { headers }
68
45
  * @returns {Promise}
69
46
  */
70
47
  delete(url, props) {
71
-
72
- return this.send({
73
- url,
74
- ...props,
75
- method: 'DELETE'
76
- });
77
-
48
+ return this.send({ url, ...props, method: 'DELETE' });
78
49
  },
79
50
 
80
51
  /**
@@ -83,77 +54,78 @@ module.exports = {
83
54
  * @param {Object} props
84
55
  * @returns {Promise}
85
56
  */
86
- send(props) {
57
+ async send(props) {
87
58
 
88
59
  const {
89
60
  url,
90
61
  body,
91
62
  method,
92
63
  responseType,
93
- headers
64
+ headers,
65
+ rejectUnauthorized
94
66
  } = typeof props === 'string'
95
- ? { path: props, method: 'get' }
67
+ ? { url: props, method: 'GET' }
96
68
  : (props || {});
97
69
 
70
+ // SSL verification is enabled by default; pass rejectUnauthorized: false to disable
71
+ const sslVerify = rejectUnauthorized !== false;
72
+
98
73
  const optHeaders = {
99
74
  'Content-Type': 'application/json',
100
- Accept: 'application/json'
75
+ Accept: 'application/json',
76
+ ...(headers || {})
101
77
  };
102
78
 
103
79
  const options = {
104
- url,
105
- method: (method || 'GET').toLowerCase(),
106
- headers: Object.assign(optHeaders, headers || {}),
107
- httpAgent: new http.Agent({ keepAlive: true, rejectUnauthorized: false }),
108
- httpsAgent: new https.Agent({ keepAlive: true, rejectUnauthorized: false }),
109
- responseType: responseType || 'json'
80
+ method: (method || 'GET').toUpperCase(),
81
+ headers: optHeaders,
82
+ dispatcher: new Agent({ connect: { rejectUnauthorized: sslVerify } })
110
83
  };
111
84
 
112
85
  if (body) {
113
- // convert body into just one line of json.
114
- options.data = JSON.parse(JSON.stringify(body || {}));
86
+ options.body = JSON.stringify(body);
115
87
  }
116
88
 
117
- return axios(options)
118
- .then( (response) => {
89
+ const target = `${options.method} ${url}`;
119
90
 
120
- const {
121
- data
122
- } = response;
91
+ try {
123
92
 
124
- return data || {};
93
+ const response = await fetch(url, options);
125
94
 
126
- })
127
- .catch( (err) => {
95
+ if (!response.ok) {
128
96
 
129
- const {
130
- response
131
- } = err || {};
97
+ let errorData = {};
98
+ try { errorData = await response.json(); } catch (_) {}
132
99
 
133
- const {
134
- data: errorData,
135
- status: statusCode
136
- } = response || {};
137
-
138
- const target = options.baseURL ? `${options.method} ${options.baseURL}/${options.url}` : `${options.method} ${options.url}`;
100
+ const { msg, message: errorMessage, error: errorMessage2 } = errorData || {};
101
+ const message = msg || errorMessage || errorMessage2 || 'Unable to connect to the Request Service';
139
102
 
140
- console.log('');
141
103
  console.log('');
142
104
  console.log('---------------------');
143
105
  console.log('ApiClient', target);
144
106
  console.log('---------------------');
145
107
 
146
- const {
147
- msg,
148
- message: errorMessage,
149
- error: errorMessage2
150
- } = errorData || {};
108
+ return VSError.reject(message, response.status || 500);
151
109
 
152
- const message = msg || errorMessage || errorMessage2 || 'Unable to connect to the Request Service';
110
+ }
111
+
112
+ if (responseType === 'arraybuffer') return response.arrayBuffer();
113
+ if (responseType === 'text') return response.text();
114
+ if (responseType === 'stream') return response.body;
115
+
116
+ const data = await response.json();
117
+ return data || {};
153
118
 
154
- return VSError.reject(message, statusCode || 500);
119
+ } catch (err) {
155
120
 
156
- });
121
+ console.log('');
122
+ console.log('---------------------');
123
+ console.log('ApiClient', target);
124
+ console.log('---------------------');
125
+
126
+ return VSError.reject(err.message || 'Unable to connect to the Request Service', 500);
127
+
128
+ }
157
129
 
158
130
  }
159
131
 
package/libs/Encrypter.js CHANGED
@@ -2,10 +2,14 @@ const crypto = require('crypto');
2
2
 
3
3
  class Encrypter {
4
4
 
5
- constructor(encryptionKey) {
5
+ constructor(encryptionKey, opts = {}) {
6
6
 
7
- this.algorithm = 'aes-256-cbc';
8
- this.key = crypto.scryptSync(encryptionKey, 'salt', 32);
7
+ const { encryption } = (typeof app !== 'undefined' && app.config) ? app.config : {};
8
+ const salt = opts.salt || (encryption && encryption.salt) || process.env.ENCRYPTION_SALT || 'vulkano-salt-v1';
9
+ const algorithm = opts.algorithm || (encryption && encryption.algorithm) || process.env.ENCRYPTION_ALGORITHM || 'aes-256-cbc';
10
+
11
+ this.algorithm = algorithm;
12
+ this.key = crypto.scryptSync(encryptionKey, salt, 32);
9
13
 
10
14
  }
11
15
 
package/libs/Filter.js CHANGED
@@ -7,7 +7,7 @@
7
7
  */
8
8
  const path = require('path');
9
9
 
10
- // Include all api controllers
10
+ // Load built-in core filters and app-level custom filters
11
11
  const coreFilters = require('include-all')({
12
12
  dirname: path.join(CORE_PATH, '/libs/filters'),
13
13
  filter: /(.+)\.js$/,
@@ -24,14 +24,14 @@ const allFilters = { ...coreFilters, ...appFilters };
24
24
 
25
25
  module.exports = {
26
26
 
27
- get(str, filters, opts) {
27
+ get(str, filters, opts) {
28
28
 
29
29
  if (Array.isArray(filters)) {
30
30
 
31
31
  let result = str;
32
-
32
+
33
33
  filters.forEach((filter) => {
34
- const f = Filter.load(filter);
34
+ const f = this.load(filter);
35
35
  result = (!f) ? '' : f.exec(result, opts);
36
36
  });
37
37
 
@@ -39,7 +39,7 @@ module.exports = {
39
39
 
40
40
  }
41
41
 
42
- const f = Filter.load(filters);
42
+ const f = this.load(filters);
43
43
  return (!f) ? '' : f.exec(str, opts);
44
44
 
45
45
  },
package/libs/Jwt.js CHANGED
@@ -1,6 +1,5 @@
1
1
  const { expressjwt: JWT } = require('express-jwt');
2
2
  const jwtSimple = require('jwt-simple');
3
- const moment = require('moment');
4
3
 
5
4
  module.exports = {
6
5
 
@@ -13,7 +12,7 @@ module.exports = {
13
12
 
14
13
  const {
15
14
  jwt,
16
- // Express config folder in app/confg/express
15
+ // Express config folder in app/config/express
17
16
  express
18
17
  } = app.config || {};
19
18
 
@@ -143,7 +142,7 @@ module.exports = {
143
142
  expiration
144
143
  } = data || {};
145
144
 
146
- const now = moment().format('x');
145
+ const now = String(Date.now());
147
146
 
148
147
  // Token expired
149
148
  if (expiration && ( Number(now) > Number(expiration) )) {
@@ -166,7 +165,7 @@ module.exports = {
166
165
  },
167
166
 
168
167
  /**
169
- * Decryp Token
168
+ * Decrypt token payload
170
169
  *
171
170
  * @param {String} str
172
171
  * @returns {String}
package/libs/Merge.js ADDED
@@ -0,0 +1,109 @@
1
+ function isMergeableObject(value) {
2
+ return value !== null
3
+ && typeof value === 'object'
4
+ && !(value instanceof RegExp)
5
+ && !(value instanceof Date);
6
+ }
7
+
8
+ function emptyTarget(val) {
9
+ return Array.isArray(val) ? [] : {};
10
+ }
11
+
12
+ function cloneUnlessOtherwiseSpecified(value, options) {
13
+ return (options.clone !== false && options.isMergeableObject(value))
14
+ ? merge(emptyTarget(value), value, options) // eslint-disable-line no-use-before-define
15
+ : value;
16
+ }
17
+
18
+ function defaultArrayMerge(target, source, options) {
19
+ return target.concat(source).map((element) => cloneUnlessOtherwiseSpecified(element, options));
20
+ }
21
+
22
+ function getMergeFunction(key, options) {
23
+ if (!options.customMerge) return merge; // eslint-disable-line no-use-before-define
24
+ const custom = options.customMerge(key);
25
+ return typeof custom === 'function' ? custom : merge; // eslint-disable-line no-use-before-define
26
+ }
27
+
28
+ function getKeys(target) {
29
+ const keys = Object.keys(target);
30
+ if (Object.getOwnPropertySymbols) {
31
+ Object.getOwnPropertySymbols(target).forEach((sym) => {
32
+ if (Object.propertyIsEnumerable.call(target, sym)) {
33
+ keys.push(sym);
34
+ }
35
+ });
36
+ }
37
+ return keys;
38
+ }
39
+
40
+ function propertyIsOnObject(object, property) {
41
+ try {
42
+ return property in object;
43
+ } catch (_) {
44
+ return false;
45
+ }
46
+ }
47
+
48
+ function propertyIsUnsafe(target, key) {
49
+ return propertyIsOnObject(target, key)
50
+ && !(Object.prototype.hasOwnProperty.call(target, key)
51
+ && Object.propertyIsEnumerable.call(target, key));
52
+ }
53
+
54
+ function mergeObject(target, source, options) {
55
+
56
+ const destination = {};
57
+
58
+ if (options.isMergeableObject(target)) {
59
+ getKeys(target).forEach((key) => {
60
+ destination[key] = cloneUnlessOtherwiseSpecified(target[key], options);
61
+ });
62
+ }
63
+
64
+ getKeys(source).forEach((key) => {
65
+
66
+ if (propertyIsUnsafe(target, key)) return;
67
+
68
+ if (propertyIsOnObject(target, key) && options.isMergeableObject(source[key])) {
69
+ destination[key] = getMergeFunction(key, options)(target[key], source[key], options);
70
+ } else {
71
+ destination[key] = cloneUnlessOtherwiseSpecified(source[key], options);
72
+ }
73
+
74
+ });
75
+
76
+ return destination;
77
+
78
+ }
79
+
80
+ function merge(target, source, options) {
81
+
82
+ const opts = options || {};
83
+ opts.arrayMerge = opts.arrayMerge || defaultArrayMerge;
84
+ opts.isMergeableObject = opts.isMergeableObject || isMergeableObject;
85
+ opts.cloneUnlessOtherwiseSpecified = cloneUnlessOtherwiseSpecified;
86
+
87
+ const sourceIsArray = Array.isArray(source);
88
+ const targetIsArray = Array.isArray(target);
89
+
90
+ if (sourceIsArray !== targetIsArray) {
91
+ return cloneUnlessOtherwiseSpecified(source, opts);
92
+ }
93
+
94
+ if (sourceIsArray) {
95
+ return opts.arrayMerge(target, source, opts);
96
+ }
97
+
98
+ return mergeObject(target, source, opts);
99
+
100
+ }
101
+
102
+ merge.all = function mergeAll(array, options) {
103
+ if (!Array.isArray(array)) {
104
+ throw new Error('first argument should be an array');
105
+ }
106
+ return array.reduce((prev, next) => merge(prev, next, options), {});
107
+ };
108
+
109
+ module.exports = merge;
package/libs/Paginate.js CHANGED
@@ -1,34 +1,43 @@
1
- const _ = require('underscore');
1
+ // Built once when the module loads: maps each base letter to a character class
2
+ // containing all its accented variants. Pre-compiling the RegExp objects avoids
3
+ // rebuilding them on every accentToRegex() call.
4
+ const _ACCENT_MAP = (() => {
5
+
6
+ const from = 'ŠŒŽšœžŸ¥µÀÁÂÃÄÅÆÇÈÉÊËẼÌÍÎÏĨÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëẽìíîïĩðñòóôõöøùúûüýÿ'.split('');
7
+ const to = 'SOZsozYYuAAAAAAACEEEEEIIIIIDNOOOOOOUUUUYsaaaaaaaceeeeeiiiiionoooooouuuuyy'.split('');
8
+
9
+ const groups = [];
10
+
11
+ to.forEach((letter, key) => {
12
+ const exist = groups.indexOf(letter);
13
+ if (exist >= 0) {
14
+ groups[exist] += from[key];
15
+ } else {
16
+ groups.push(letter);
17
+ }
18
+ });
19
+
20
+ return groups.map((rg, key) => ({
21
+ charClass: new RegExp(`[${rg}]`),
22
+ placeholder: new RegExp(`_${key}_`),
23
+ expanded: `[${rg}]`,
24
+ key
25
+ }));
26
+
27
+ })();
2
28
 
3
29
  module.exports = {
4
30
 
5
31
  accentToRegex(_text) {
6
32
 
7
- const ACCENT_STRINGS = 'ŠŒŽšœžŸ¥µÀÁÂÃÄÅÆÇÈÉÊËẼÌÍÎÏĨÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëẽìíîïĩðñòóôõöøùúûüýÿ';
8
- const NO_ACCENT_STRINGS = 'SOZsozYYuAAAAAAACEEEEEIIIIIDNOOOOOOUUUUYsaaaaaaaceeeeeiiiiionoooooouuuuyy';
9
-
10
- const from = ACCENT_STRINGS.split('');
11
- const to = NO_ACCENT_STRINGS.split('');
12
- const result = [];
13
33
  let text = _text;
14
34
 
15
- to.forEach( (letter, key) => {
16
- const exist = result.indexOf(letter);
17
- if (exist >= 0) {
18
- result[exist] += from[key];
19
- } else {
20
- result.push(letter);
21
- }
35
+ _ACCENT_MAP.forEach(({ charClass, key }) => {
36
+ text = text.replace(charClass, `_${key}_`);
22
37
  });
23
38
 
24
- result.forEach( (rg, key) => {
25
- const regex = new RegExp(`[${rg}]`);
26
- text = text.replace(regex, `_${key}_`);
27
- });
28
-
29
- result.forEach( (rg, key) => {
30
- const regex = new RegExp(`_${key}_`);
31
- text = text.replace(regex, `[${rg}]`);
39
+ _ACCENT_MAP.forEach(({ placeholder, expanded }) => {
40
+ text = text.replace(placeholder, expanded);
32
41
  });
33
42
 
34
43
  return text;
@@ -50,13 +59,11 @@ module.exports = {
50
59
  const search = query.search || props.search || null;
51
60
  const searchType = (query.searchType || '').toLowerCase().replace('-', '');
52
61
 
53
- const result = _.omit({
54
- page,
55
- perPage,
56
- fields,
57
- sort,
58
- search
59
- }, (value) => !value );
62
+ const result = Object.fromEntries(
63
+ Object.entries({
64
+ page, perPage, fields, sort, search
65
+ }).filter(([, v]) => v)
66
+ );
60
67
 
61
68
  // Filter by search
62
69
  const searchBy = props.searchBy || [];
@@ -77,12 +84,16 @@ module.exports = {
77
84
 
78
85
  if (type === String) {
79
86
 
87
+ const safeSearch = this.accentToRegex(
88
+ search.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&')
89
+ );
90
+
80
91
  if (searchType === 'startwith' || searchType === 'start') {
81
- row[item] = new RegExp(['^', this.accentToRegex(search), '*.'].join(''), 'i');
92
+ row[item] = new RegExp(`^${safeSearch}`, 'i');
82
93
  } else if (searchType === 'endwith' || searchType === 'end') {
83
- row[item] = new RegExp(['.*', this.accentToRegex(search)].join(''), 'i');
94
+ row[item] = new RegExp(`${safeSearch}$`, 'i');
84
95
  } else {
85
- row[item] = new RegExp(['.*', this.accentToRegex(search), '*.'].join(''), 'i');
96
+ row[item] = new RegExp(safeSearch, 'i');
86
97
  }
87
98
 
88
99
  } else if (type === Number) {
@@ -199,7 +210,7 @@ module.exports = {
199
210
 
200
211
  const populate = hasPopulate || [];
201
212
 
202
- let criteria = query || {};
213
+ const criteria = query || {};
203
214
 
204
215
  // Setup
205
216
  const page = criteria.page === 'all' ? 'all' : ( parseInt(criteria.page, 10) || 1);
@@ -236,16 +247,16 @@ module.exports = {
236
247
  delete criteria.sort;
237
248
 
238
249
  if (criteria.search) {
239
- criteria = _.extend(criteria, criteria.search);
250
+ Object.assign(criteria, criteria.search);
240
251
  delete criteria.search;
241
252
  }
242
253
 
243
254
  const optPopulate = this.getPopulatedCollections(populate || []);
244
- const queryModel = _.extend(criteria, {});
255
+ const queryModel = { ...criteria };
245
256
 
246
257
  if (page === 'all') {
247
258
 
248
- const optsModel = _.extend(sort, { populate: optPopulate });
259
+ const optsModel = { ...sort, populate: optPopulate };
249
260
  return Model.find(queryModel, fields.join(' '), optsModel);
250
261
 
251
262
  }
@@ -265,7 +276,7 @@ module.exports = {
265
276
 
266
277
  opts.populate = optPopulate;
267
278
 
268
- const optsModel = _.extend(opts, sort);
279
+ const optsModel = { ...opts, ...sort };
269
280
 
270
281
  return Model
271
282
  .paginate(queryModel, optsModel)
@@ -282,7 +293,7 @@ module.exports = {
282
293
 
283
294
  let cursor = (page > 1) ? ((page * perPage) - (perPage - 1)) : 1;
284
295
 
285
- const tmpNext = (((perPage * (page - 1)) + perPage) <= total);
296
+ const tmpNext = (((perPage * (page - 1)) + perPage) < total);
286
297
  const next = tmpNext ? (page + 1) : false;
287
298
  let prev = (page > 1) ? (page - 1) : false;
288
299
  const totalPages = Math.ceil(total / perPage);
package/libs/VSError.js CHANGED
@@ -1,5 +1,3 @@
1
- const Promise = require('bluebird');
2
-
3
1
  function VSError(msg, code, props) {
4
2
 
5
3
  Error.captureStackTrace(this, this.constructor);
package/libs/i18n.js CHANGED
@@ -1,9 +1,4 @@
1
1
  const i18next = require('i18next');
2
- const moment = require('moment');
3
-
4
- require('moment/min/locales.min');
5
-
6
- moment.locale('en');
7
2
 
8
3
  module.exports = (() => {
9
4
 
@@ -35,17 +30,6 @@ module.exports = (() => {
35
30
  resources: Object.fromEntries(resources)
36
31
  });
37
32
 
38
- // catch the event and make changes accordingly
39
- i18next.on('languageChanged', (lng) => {
40
-
41
- try {
42
- moment.locale(lng);
43
- } catch (err) {
44
- console.log(err);
45
- }
46
-
47
- });
48
-
49
33
  return i18next;
50
34
 
51
35
  })();