vtlab-generic-functions 1.0.4

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/utils/index.js ADDED
@@ -0,0 +1,579 @@
1
+ const { stringify } = require("flatted");
2
+ const countriesQuery = require("countries-code");
3
+ const config = require("../config");
4
+ const awslambda = require("../lambda");
5
+ const lookup = require("country-data").lookup;
6
+
7
+ /**
8
+ * Access object with key as a dot notation.
9
+ * @param obj Eg object is {depth0: {depth1: depth2: 'valueOfDepth2'}}
10
+ * @param key string of 'depth0.depth1.depth2'
11
+ * @returns {*} values of obj.depth0.depth1.depth2
12
+ */
13
+ const getObjectByString = (obj, key) => {
14
+ const keys = key.split(".");
15
+ let temp = obj;
16
+ for (let i = 0; i < keys.length; i++) {
17
+ temp = temp[keys[i]];
18
+ }
19
+ return temp;
20
+ };
21
+
22
+ const dataToString = (payload, type = "error") => {
23
+ try {
24
+ if (payload.response) {
25
+ //Often return from axios request
26
+ return { res: stringify(payload.response) };
27
+ }
28
+ if (payload instanceof Error) {
29
+ return { res: JSON.stringify(payload.message) };
30
+ }
31
+ if (typeof payload === "string") {
32
+ return { res: JSON.stringify(payload) };
33
+ }
34
+ switch (type) {
35
+ case "error":
36
+ let { code, ...err } = payload;
37
+ return {
38
+ code,
39
+ res:
40
+ err instanceof Error
41
+ ? JSON.stringify(err.message)
42
+ : JSON.stringify(err),
43
+ };
44
+ case "data":
45
+ return { res: JSON.stringify(payload) };
46
+ default:
47
+ return { res: JSON.stringify(payload) };
48
+ }
49
+ } catch (err) {
50
+ let strToReturn = "Error while stringifying";
51
+ try {
52
+ strToReturn = stringify(payload);
53
+ } catch (err) {
54
+ return { res: strToReturn };
55
+ }
56
+ return { res: strToReturn };
57
+ }
58
+ };
59
+
60
+ const getMessageFromValidator = (validator) => {
61
+ let messageToReturn = "Something went badly wrong validating request format";
62
+ if (validator.error && validator.error.message) {
63
+ if (typeof validator.error.message === "string") {
64
+ messageToReturn = messageToReturn + ":" + validator.error.message;
65
+ } else if (Array.isArray(validator.error.message)) {
66
+ messageToReturn =
67
+ messageToReturn + " [" + validator.error.message.join(";") + "]";
68
+ }
69
+ } else {
70
+ messageToReturn = "Everything looks OK";
71
+ }
72
+ return messageToReturn;
73
+ };
74
+
75
+ const sleep = (ms) => {
76
+ return new Promise((resolve) => {
77
+ setTimeout(resolve, ms);
78
+ });
79
+ };
80
+
81
+ const replaceInvalidXMLChars = (inputString, useHTMLEntities = false) => {
82
+ if (!inputString || typeof inputString !== "string") {
83
+ return inputString;
84
+ }
85
+ if (useHTMLEntities) {
86
+ inputString = inputString.replace(/&/g, "&amp;");
87
+ inputString = inputString.replace(/</g, "&lt;");
88
+ inputString = inputString.replace(/>/g, "&gt;");
89
+ inputString = inputString.replace(/"/g, "&quot;");
90
+ inputString = inputString.replace(/'/g, "&apos;");
91
+ } else {
92
+ inputString = inputString.replace(/&/g, "");
93
+ inputString = inputString.replace(/</g, "");
94
+ inputString = inputString.replace(/>/g, "");
95
+ inputString = inputString.replace(/"/g, "");
96
+ inputString = inputString.replace(/'/g, "");
97
+ }
98
+
99
+ return inputString;
100
+ }; //replaceInvalidXMLChars
101
+
102
+ // wrapper function for countriesQuery lib
103
+ const getCountryNameByCode = (CountryCode = "") => {
104
+ try {
105
+ let country = countriesQuery.getCountry(CountryCode);
106
+ return country && country !== "Wrong Country Code Input" ? country : ""; //remove static error message from library
107
+ } catch (err) {
108
+ return "";
109
+ }
110
+ };
111
+
112
+ const getCountryCodeByName = (CountryName = "") => {
113
+ try {
114
+ let country = lookup.countries({ name: CountryName });
115
+ return country && country.length > 0 ? country[0].alpha2 : ""; //remove static error message from library
116
+ } catch (err) {
117
+ return "";
118
+ }
119
+ };
120
+
121
+ // wrapper function for countriesQuery lib
122
+ const convertAlphaCode = (countryCode = "") => {
123
+ try {
124
+ let alphaCode = countriesQuery.convertAlphaCode(countryCode.trim());
125
+ return alphaCode && alphaCode !== "Wrong Country Code Input"
126
+ ? alphaCode
127
+ : ""; //remove static error message from library
128
+ } catch (err) {
129
+ return "";
130
+ }
131
+ };
132
+
133
+ let processCountry = (country, countryIso) => {
134
+ try {
135
+ if (country && (!countryIso || countryIso === "")) {
136
+ // case1: country is > 3chars
137
+ if (country.length > 3) {
138
+ return {
139
+ country,
140
+ isoCode: getCountryCodeByName(country),
141
+ };
142
+ } else {
143
+ // case 2: country is <= 3 chars
144
+ return {
145
+ country: getCountryNameByCode(country),
146
+ isoCode: country.length === 3 ? convertAlphaCode(country) : country,
147
+ };
148
+ }
149
+ }
150
+ if (country && countryIso && country !== "" && countryIso !== "") {
151
+ let a, b;
152
+ // case1: country is > 3chars
153
+ if (country.length > 3) {
154
+ a = country;
155
+ } else {
156
+ // case 2: country is <= 3 chars
157
+ a = getCountryNameByCode(countryIso);
158
+ }
159
+ // case 3: countryIso >= 3 chars => transform to iso 2
160
+ if (countryIso.length >= 3) {
161
+ b = convertAlphaCode(countryIso);
162
+ } else {
163
+ b = countryIso;
164
+ }
165
+ return { country: a, isoCode: b };
166
+ }
167
+ if ((!country || country === "") && countryIso) {
168
+ let a, b;
169
+ // case 1: countryIso >= 3 chars => transform to iso 2
170
+ if (countryIso.length >= 3) {
171
+ b = convertAlphaCode(countryIso);
172
+ } else {
173
+ b = countryIso;
174
+ }
175
+ // infers country from countryIso
176
+ a = getCountryNameByCode(countryIso);
177
+ return { country: a, isoCode: b };
178
+ }
179
+ if ((!country || country === "") && (!countryIso || !countryIso === "")) {
180
+ throw "Error";
181
+ }
182
+ } catch (e) {
183
+ return { country: "", isoCode: "" };
184
+ }
185
+ };
186
+
187
+ const fixSizeField = (
188
+ data,
189
+ {
190
+ type = "string",
191
+ size = 3,
192
+ nDecimalDigits = 1,
193
+ completeOnTheRight = true,
194
+ stringPlaceholder = " ",
195
+ } = {}
196
+ ) => {
197
+ let res = "";
198
+ data = data ? String(data) : "";
199
+ let completeDirection = (value, placeholder, size, completeOnTheRight) => {
200
+ if (completeOnTheRight) {
201
+ return value + String(placeholder).repeat(size);
202
+ } else {
203
+ return String(placeholder).repeat(size) + value;
204
+ }
205
+ };
206
+
207
+ switch (type) {
208
+ case "string":
209
+ {
210
+ if (!data) data = "";
211
+ data = data.toString();
212
+ let needToCompleteSize = size - data.length;
213
+ if (needToCompleteSize < 0) {
214
+ res = data.substring(0, size);
215
+ } else {
216
+ res = completeDirection(
217
+ data,
218
+ stringPlaceholder,
219
+ needToCompleteSize,
220
+ completeOnTheRight
221
+ );
222
+ }
223
+ }
224
+ break;
225
+ case "number":
226
+ {
227
+ if (!data) data = 0;
228
+ let numberToString = data.toString();
229
+ let intPart = numberToString.split(".")[0];
230
+ let decPart = numberToString.split(".")[1];
231
+ //calculate decimal part of the number
232
+ if (!decPart) {
233
+ decPart = "0".repeat(nDecimalDigits);
234
+ } else {
235
+ let needToCompleteDecPart = nDecimalDigits - decPart.length;
236
+ if (needToCompleteDecPart < 0) {
237
+ decPart = decPart.substring(0, nDecimalDigits);
238
+ } else {
239
+ decPart = decPart + "0".repeat(needToCompleteDecPart);
240
+ }
241
+ }
242
+ res = intPart;
243
+ if (nDecimalDigits > 0) {
244
+ res += "." + decPart;
245
+ }
246
+ let needToCompleteSize = size - res.length;
247
+ if (needToCompleteSize < 0) {
248
+ res = res.substring(0, size);
249
+ } else {
250
+ res = "0".repeat(needToCompleteSize) + res;
251
+ }
252
+ }
253
+ break;
254
+ default:
255
+ }
256
+ return res;
257
+ };
258
+
259
+ const getValueOrNull = (_value, path) => {
260
+ try {
261
+ let value = { ..._value };
262
+ // check that the value has a value
263
+ if (!value || !Object.keys(value).length || value === "") {
264
+ return null;
265
+ }
266
+ return path
267
+ .split(/\].|\]\[|\[|\./) // split on [] or .
268
+ .map((el) =>
269
+ el
270
+ .replace(/\]$/, "")
271
+ //In case you want to parse an ESCAPE propertie with [] or . in the name, you should use
272
+ // () instead of [] and , instead of .
273
+ .replace("(", "[")
274
+ .replace(")", "]")
275
+ .replace(",", ".")
276
+ )
277
+ .reduce(
278
+ (obj, el) =>
279
+ obj && typeof obj !== "undefined" && obj[el] ? obj[el] : null,
280
+ value
281
+ );
282
+ } catch (e) {
283
+ console.log(e);
284
+ throw `Error trying to get Path: ${path} in value`;
285
+ }
286
+ };
287
+
288
+ // allows to return a value in case of null and also to validate if its a required field or not
289
+ const getValueOrNullControl = (
290
+ value = {},
291
+ path = "",
292
+ isRequired = true,
293
+ nullCase = ""
294
+ ) => {
295
+ try {
296
+ const result = getValueOrNull(value, path);
297
+ if (!result && isRequired)
298
+ throw new Error(`Value path ${path} is required`);
299
+ if (!result && !isRequired && nullCase != null && nullCase !== undefined)
300
+ return nullCase;
301
+ return result;
302
+ } catch (e) {
303
+ throw new Error(`Failed trying to get value with path ${path}`);
304
+ }
305
+ };
306
+
307
+ // use it to create safe fields for csv files writing
308
+ const generateCSVField = (
309
+ obj,
310
+ path,
311
+ separator = ";",
312
+ lastValue = false,
313
+ replaceValue = " ",
314
+ alternativeValue = { path: null, obj: null }
315
+ ) => {
316
+ let value = path ? getValueOrNull(obj, path) : obj;
317
+ if (value) {
318
+ if (
319
+ typeof value === "string" ||
320
+ typeof value === "number" ||
321
+ typeof value === "boolean" ||
322
+ typeof value === "bigint"
323
+ ) {
324
+ value = String(value).replace(
325
+ new RegExp(`\n|\r\n|\r|\f|${separator}`, "g"),
326
+ replaceValue
327
+ );
328
+ } else {
329
+ value = "";
330
+ }
331
+ if (!lastValue) {
332
+ return value + separator;
333
+ }
334
+ return value;
335
+ } else if (alternativeValue && alternativeValue.path) {
336
+ value = generateCSVField(
337
+ alternativeValue.obj ?? obj,
338
+ alternativeValue.path,
339
+ separator,
340
+ lastValue,
341
+ replaceValue
342
+ );
343
+ return value;
344
+ }
345
+ return separator;
346
+ };
347
+
348
+ const promisesCollector = (elements, fn, params) => {
349
+ // set vars
350
+ let promises = [];
351
+
352
+ // validations
353
+ if (!Array.isArray(elements) || elements.length < 1) {
354
+ // throw error
355
+ throw "Wrong input data";
356
+ }
357
+
358
+ // iterate over every element
359
+ for (let element of elements) {
360
+ // push the promise to the array of promises
361
+ promises.push(fn(element, params));
362
+ }
363
+
364
+ // return promises result as a promise
365
+ return Promise.all(promises);
366
+ };
367
+
368
+ const batchPromiseCollector = async (
369
+ elements,
370
+ fn,
371
+ params = {},
372
+ batchSize = 10
373
+ ) => {
374
+ for (let i = 0; i < elements.length; i += batchSize) {
375
+ const requests = elements.slice(i, i + batchSize).map((item) => {
376
+ if (item && typeof item === "object") {
377
+ return fn({ ...item, ...params });
378
+ } else {
379
+ return fn(item, params);
380
+ }
381
+ });
382
+ await Promise.all(requests);
383
+ }
384
+ };
385
+
386
+ const batchPromiseCollectorWithIndex = async (
387
+ elements,
388
+ fn,
389
+ params = {},
390
+ batchSize = 10
391
+ ) => {
392
+ for (let i = 0; i < elements.length; i += batchSize) {
393
+ const requests = elements.slice(i, i + batchSize).map((item, index) => {
394
+ return fn(item, i + index, params);
395
+ });
396
+ await Promise.all(requests);
397
+ }
398
+ };
399
+
400
+ /**
401
+ * Group an array of objects by a property
402
+ * @param array
403
+ * @param key
404
+ * @returns {{}}
405
+ */
406
+ const groupBy = (array, key) => {
407
+ // set vars
408
+ let result = {};
409
+
410
+ // validations
411
+ if (!Array.isArray(array) || array.length < 1) {
412
+ // throw error
413
+ throw "Wrong input data";
414
+ }
415
+
416
+ // iterate over every element
417
+ for (let element of array) {
418
+ // set the key
419
+ let keyValue = element[key];
420
+
421
+ // if the key is not set
422
+ if (!keyValue) {
423
+ // throw error
424
+ throw "Wrong input data";
425
+ }
426
+
427
+ // if the key is not set
428
+ if (!result[keyValue]) {
429
+ // set the key
430
+ result[keyValue] = [];
431
+ }
432
+
433
+ // push the element to the array of elements
434
+ result[keyValue].push(element);
435
+ }
436
+
437
+ // return the result
438
+ return result;
439
+ };
440
+
441
+ const generateUuid = (length = 8) => {
442
+ // create string of a fixed length
443
+ let value = new Array(length + 1).join("x");
444
+ return value.replace(/[xy]/g, function (c) {
445
+ let r = (Math.random() * 16) | 0,
446
+ v = c == "x" ? r : (r & 0x3) | 0x8;
447
+ return v.toString(16);
448
+ });
449
+ };
450
+
451
+ /**
452
+ * Merge PDF
453
+ * @param path
454
+ * @param s3Pointers
455
+ * @param extension
456
+ * @returns {Promise<unknown>}
457
+ */
458
+ const mergePDF = async (path, s3Pointers, extension) => {
459
+ let credentials = await config.get();
460
+
461
+ if (!credentials || !credentials.microservices) {
462
+ throw "No credentials loaded";
463
+ }
464
+
465
+ let mergePDFFunction = credentials.microservices.mergePDF.functionName;
466
+
467
+ let lambdaStageVariable = {
468
+ S3BUCKET: credentials.s3bucket,
469
+ ACCESS_KEY_ID: credentials.awsCredentials.accessKeyId,
470
+ SECRET_ACCESS_KEY: credentials.awsCredentials.secretAccessKey,
471
+ };
472
+ return new Promise(async (resolve, reject) => {
473
+ try {
474
+ if (Array.isArray(s3Pointers) && s3Pointers.length > 1) {
475
+ if (!path) {
476
+ throw new Error('"mergePDF" invalid path');
477
+ }
478
+ extension = extension ? extension : "pdf";
479
+ let payload = JSON.stringify({
480
+ s3files: s3Pointers,
481
+ stageVariables: lambdaStageVariable,
482
+ responseOption: {
483
+ type: "S3Path",
484
+ signedURL: false,
485
+ saveLocation: `${path}${new Date().getTime()}.${extension}`,
486
+ },
487
+ });
488
+
489
+ let lambdaResult = await awslambda.invokeLambda(
490
+ payload,
491
+ mergePDFFunction
492
+ );
493
+
494
+ if (lambdaResult.statusCode !== 200) {
495
+ throw new Error(
496
+ `Lambda function ${mergePDFFunction} returned status code ${lambdaResult.statusCode}`
497
+ );
498
+ }
499
+ resolve(lambdaResult.file);
500
+ } else {
501
+ throw new Error('"mergePDF" invalid array');
502
+ }
503
+ } catch (error) {
504
+ reject(error);
505
+ }
506
+ });
507
+ };
508
+
509
+ const getMessageAndCodeFromError = (err) => {
510
+ const returnData = {};
511
+
512
+ //if the error is string
513
+ if (typeof err === "string") {
514
+ try {
515
+ err = JSON.parse(err);
516
+ } catch (e) {
517
+ return {
518
+ message: err,
519
+ code: 417,
520
+ };
521
+ }
522
+ }
523
+
524
+ //axios normal error
525
+ if (
526
+ getValueOrNull(err, "response.status") &&
527
+ getValueOrNull(err, "response.data")
528
+ ) {
529
+ return {
530
+ message: err.response.data,
531
+ code: err.response.status,
532
+ };
533
+ }
534
+
535
+ //if is axios error in array
536
+ if (err && Array.isArray(err) && err.length > 0) {
537
+ returnData.code = err[0].status;
538
+ returnData.message = err[err[0].data];
539
+ } else if (err.message) {
540
+ //normal error
541
+ returnData.message = err.message;
542
+ if (err.code) {
543
+ returnData.code = err.code;
544
+ } else if (getValueOrNull(err, "response.status")) {
545
+ returnData.code = err.response.status;
546
+ } else {
547
+ //set default code
548
+ returnData.code = 417;
549
+ }
550
+ } else {
551
+ //default
552
+ returnData.message = err;
553
+ returnData.code = 417;
554
+ }
555
+ return returnData;
556
+ };
557
+
558
+ module.exports = {
559
+ dataToString,
560
+ getMessageFromValidator,
561
+ getValueOrNull,
562
+ getObjectByString,
563
+ sleep,
564
+ replaceInvalidXMLChars,
565
+ getCountryNameByCode,
566
+ convertAlphaCode,
567
+ getCountryCodeByName,
568
+ processCountry,
569
+ fixSizeField,
570
+ getValueOrNullControl,
571
+ generateCSVField,
572
+ promisesCollector,
573
+ batchPromiseCollector,
574
+ batchPromiseCollectorWithIndex,
575
+ groupBy,
576
+ generateUuid,
577
+ mergePDF,
578
+ getMessageAndCodeFromError,
579
+ };
@@ -0,0 +1,44 @@
1
+ const joiValidations = require('./joiSchema');
2
+
3
+
4
+ /**
5
+ * To validate the data with Joi Schema. if this is true it throw error validation to the try catch. If it's false it just return the validation instead of throw.
6
+ * If the validation is for Request we need to send the validation message to the user. If it's the response validation we don't need to send to the user.
7
+ * @param data data needs to be validated
8
+ * @param schema schema used to validate
9
+ * @param canThrow Boolean to throw the error result
10
+ * @returns {*} throw error to the catch || return validated data
11
+ */
12
+ const validateWithJoi = (data, schema, canThrow = true) => {
13
+ if(!schema) throw new Error('Schema was not found')
14
+
15
+ // validate the schema
16
+ const validationResult = schema.validate(data, {abortEarly: false, stripUnknown: true});
17
+
18
+ //Throw error back the api-gateway (user request)
19
+ if (canThrow) {
20
+ if (validationResult.error) {
21
+ throw ({
22
+ message: validationResult.error.message
23
+ });
24
+ }
25
+ return validationResult.value;
26
+
27
+ // return validation result to function. Did not throw to the user request
28
+ } else {
29
+ return validationResult;
30
+ }
31
+ };
32
+
33
+
34
+ // exportable methods
35
+ const validateRequest = (event, model,canThrow = true) => validateWithJoi(event, joiValidations[model],canThrow);
36
+ const validate = (event, model,canThrow = true) => validateWithJoi(event, joiValidations[model],canThrow);
37
+ const validateResponse = (event, model) => validateWithJoi(event, joiValidations[model], false);
38
+
39
+ module.exports = {
40
+ validateRequest: validateRequest,
41
+ validate: validate,
42
+ validateResponse: validateResponse,
43
+ validateWithJoi: validateWithJoi
44
+ };