@diia-inhouse/utils 5.3.29 → 6.0.8
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/dist/address.d.ts +7 -0
- package/dist/address.js +38 -41
- package/dist/applicationUtils.d.ts +225 -0
- package/dist/applicationUtils.js +576 -678
- package/dist/asserts.d.ts +6 -0
- package/dist/asserts.js +12 -19
- package/dist/dictionaries/phoneCodes.js +2435 -2442
- package/dist/guards.d.ts +22 -0
- package/dist/guards.js +58 -74
- package/dist/index.d.ts +36 -0
- package/dist/index.js +34 -70
- package/dist/integration.d.ts +6 -0
- package/dist/integration.js +7 -10
- package/dist/interfaces/asserts.d.ts +9 -0
- package/dist/interfaces/publicService.d.ts +11 -0
- package/dist/interfaces/slack.d.ts +6 -0
- package/dist/network.d.ts +17 -0
- package/dist/network.js +64 -72
- package/dist/payment.d.ts +8 -0
- package/dist/payment.js +19 -22
- package/dist/pdfUtils.d.ts +6 -0
- package/dist/pdfUtils.js +7 -10
- package/dist/phoneticChecker/index.d.ts +9 -0
- package/dist/phoneticChecker/index.js +18 -23
- package/dist/phoneticChecker/metaphone.js +38 -41
- package/dist/publicService.d.ts +59 -0
- package/dist/publicService.js +137 -150
- package/dist/random.d.ts +7 -0
- package/dist/random.js +11 -10
- package/dist/session.d.ts +7 -0
- package/dist/session.js +10 -19
- package/dist/slackFormatting.d.ts +13 -0
- package/dist/slackFormatting.js +41 -24
- package/dist/typeUtils.d.ts +8 -0
- package/dist/typeUtils.js +13 -15
- package/package.json +42 -27
- package/dist/address.js.map +0 -1
- package/dist/applicationUtils.js.map +0 -1
- package/dist/asserts.js.map +0 -1
- package/dist/dictionaries/phoneCodes.js.map +0 -1
- package/dist/guards.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/integration.js.map +0 -1
- package/dist/interfaces/applicationUtils.js +0 -3
- package/dist/interfaces/applicationUtils.js.map +0 -1
- package/dist/interfaces/asserts.js +0 -3
- package/dist/interfaces/asserts.js.map +0 -1
- package/dist/interfaces/error.js +0 -3
- package/dist/interfaces/error.js.map +0 -1
- package/dist/interfaces/publicService.js +0 -12
- package/dist/interfaces/publicService.js.map +0 -1
- package/dist/interfaces/slack.js +0 -3
- package/dist/interfaces/slack.js.map +0 -1
- package/dist/network.js.map +0 -1
- package/dist/payment.js.map +0 -1
- package/dist/pdfUtils.js.map +0 -1
- package/dist/phoneticChecker/index.js.map +0 -1
- package/dist/phoneticChecker/metaphone.js.map +0 -1
- package/dist/publicService.js.map +0 -1
- package/dist/random.js.map +0 -1
- package/dist/session.js.map +0 -1
- package/dist/slackFormatting.js.map +0 -1
- package/dist/typeUtils.js.map +0 -1
- package/dist/types/address.d.ts +0 -4
- package/dist/types/applicationUtils.d.ts +0 -212
- package/dist/types/asserts.d.ts +0 -2
- package/dist/types/dictionaries/phoneCodes.d.ts +0 -2
- package/dist/types/guards.d.ts +0 -18
- package/dist/types/index.d.ts +0 -86
- package/dist/types/integration.d.ts +0 -3
- package/dist/types/interfaces/applicationUtils.d.ts +0 -1
- package/dist/types/interfaces/asserts.d.ts +0 -5
- package/dist/types/interfaces/error.d.ts +0 -7
- package/dist/types/interfaces/publicService.d.ts +0 -15
- package/dist/types/interfaces/slack.d.ts +0 -3
- package/dist/types/network.d.ts +0 -13
- package/dist/types/payment.d.ts +0 -5
- package/dist/types/pdfUtils.d.ts +0 -3
- package/dist/types/phoneticChecker/index.d.ts +0 -7
- package/dist/types/phoneticChecker/metaphone.d.ts +0 -6
- package/dist/types/publicService.d.ts +0 -55
- package/dist/types/random.d.ts +0 -5
- package/dist/types/session.d.ts +0 -3
- package/dist/types/slackFormatting.d.ts +0 -7
- package/dist/types/typeUtils.d.ts +0 -5
package/dist/applicationUtils.js
CHANGED
|
@@ -1,679 +1,577 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { compare } from "compare-versions";
|
|
4
|
+
import lodash from "lodash";
|
|
5
|
+
import { DateTime } from "luxon";
|
|
6
|
+
import mapAgeCleaner from "map-age-cleaner";
|
|
7
|
+
import { ApiError, BadRequestError } from "@diia-inhouse/errors";
|
|
8
|
+
import { ActionVersion, Gender, HttpStatusCode, SessionType } from "@diia-inhouse/types";
|
|
9
|
+
//#region src/applicationUtils.ts
|
|
10
|
+
const { camelCase, cloneDeep, kebabCase, startCase } = lodash;
|
|
11
|
+
var ApplicationUtils = class ApplicationUtils {
|
|
12
|
+
static documentTypeToCamelCaseExceptions = {
|
|
13
|
+
["student-id-card"]: "studentCard",
|
|
14
|
+
["internal-passport"]: "idCard"
|
|
15
|
+
};
|
|
16
|
+
static camelCaseToDocumentTypeExceptions = {
|
|
17
|
+
["studentCard"]: "student-id-card",
|
|
18
|
+
["idCard"]: "internal-passport"
|
|
19
|
+
};
|
|
20
|
+
static defaultItnDate = "31.12.1899";
|
|
21
|
+
static defaultFormat = "dd.MM.yyyy";
|
|
22
|
+
static cyrillicToLatin = new Map([
|
|
23
|
+
["і", "i"],
|
|
24
|
+
["е", "e"],
|
|
25
|
+
["у", "y"],
|
|
26
|
+
["и", "u"],
|
|
27
|
+
["о", "o"],
|
|
28
|
+
["р", "p"],
|
|
29
|
+
["а", "a"],
|
|
30
|
+
["к", "k"],
|
|
31
|
+
["х", "x"],
|
|
32
|
+
["с", "c"],
|
|
33
|
+
["І", "I"],
|
|
34
|
+
["Е", "E"],
|
|
35
|
+
["Т", "T"],
|
|
36
|
+
["О", "O"],
|
|
37
|
+
["Р", "P"],
|
|
38
|
+
["А", "A"],
|
|
39
|
+
["Н", "H"],
|
|
40
|
+
["К", "K"],
|
|
41
|
+
["Х", "X"],
|
|
42
|
+
["С", "C"],
|
|
43
|
+
["В", "B"],
|
|
44
|
+
["М", "M"],
|
|
45
|
+
["У", "Y"]
|
|
46
|
+
]);
|
|
47
|
+
static nameComponentsToLowerCase = [
|
|
48
|
+
"огли",
|
|
49
|
+
"оглу",
|
|
50
|
+
"заде",
|
|
51
|
+
"кизи",
|
|
52
|
+
"бей",
|
|
53
|
+
"бек",
|
|
54
|
+
"паша",
|
|
55
|
+
"хан",
|
|
56
|
+
"шах",
|
|
57
|
+
"мелік",
|
|
58
|
+
"зуль",
|
|
59
|
+
"ібн",
|
|
60
|
+
"тер",
|
|
61
|
+
"аль",
|
|
62
|
+
"ель",
|
|
63
|
+
"аш",
|
|
64
|
+
"ас",
|
|
65
|
+
"ад",
|
|
66
|
+
"ель",
|
|
67
|
+
"ес"
|
|
68
|
+
];
|
|
69
|
+
static documentTypeToCamelCase(documentType) {
|
|
70
|
+
return ApplicationUtils.documentTypeToCamelCaseExceptions[documentType] || camelCase(documentType);
|
|
71
|
+
}
|
|
72
|
+
static camelCaseToDocumentType(camelCaseDocumentType) {
|
|
73
|
+
return ApplicationUtils.camelCaseToDocumentTypeExceptions[camelCaseDocumentType] || kebabCase(camelCaseDocumentType);
|
|
74
|
+
}
|
|
75
|
+
static isItnChecksumValid(itn) {
|
|
76
|
+
if (itn.length !== 10) return false;
|
|
77
|
+
const ITN_POLY = [
|
|
78
|
+
-1,
|
|
79
|
+
5,
|
|
80
|
+
7,
|
|
81
|
+
9,
|
|
82
|
+
4,
|
|
83
|
+
6,
|
|
84
|
+
10,
|
|
85
|
+
5,
|
|
86
|
+
7
|
|
87
|
+
];
|
|
88
|
+
const digits = Array.from(itn, Number);
|
|
89
|
+
const checksum = ITN_POLY.reduce((a, x, i) => a + x * digits[i], 0);
|
|
90
|
+
const controlDigit = (checksum - 11 * Math.floor(checksum / 11)) % 10;
|
|
91
|
+
return digits.at(-1) === controlDigit;
|
|
92
|
+
}
|
|
93
|
+
static getBirthDayFromItn(itn) {
|
|
94
|
+
if (itn.length !== 10) return "";
|
|
95
|
+
const days = Number.parseInt(itn.slice(0, 5), 10);
|
|
96
|
+
return DateTime.fromFormat(this.defaultItnDate, this.defaultFormat).plus({ days }).toFormat(this.defaultFormat);
|
|
97
|
+
}
|
|
98
|
+
static getGenderFromItn(itn) {
|
|
99
|
+
return Number(itn.at(-2)) % 2 === 0 ? Gender.female : Gender.male;
|
|
100
|
+
}
|
|
101
|
+
static isItnFormatValid(itn) {
|
|
102
|
+
if (/^0{1,20}$/.test(itn) || itn === "null" || !/^\d{10}$/.test(itn)) return false;
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
static capitalizeName(name) {
|
|
106
|
+
if (!name) return "";
|
|
107
|
+
const loweredNameParts = name.toLowerCase().split(" ");
|
|
108
|
+
if (loweredNameParts.length === 1) {
|
|
109
|
+
const [namePart] = loweredNameParts;
|
|
110
|
+
if (this.nameComponentsToLowerCase.includes(namePart)) return this.capitalizeFirstLetter(namePart);
|
|
111
|
+
}
|
|
112
|
+
return loweredNameParts.map((word) => word.split("-").map((namePart) => this.nameComponentsToLowerCase.includes(namePart) ? namePart : this.capitalizeFirstLetter(namePart)).join("-")).join(" ");
|
|
113
|
+
}
|
|
114
|
+
static capitalizeFirstLetter(str) {
|
|
115
|
+
if (typeof str !== "string") return "";
|
|
116
|
+
return `${str.charAt(0).toUpperCase()}${str.slice(1)}`;
|
|
117
|
+
}
|
|
118
|
+
static lowerFirstLetter(str) {
|
|
119
|
+
if (typeof str !== "string") return "";
|
|
120
|
+
return `${str.charAt(0).toLowerCase()}${str.slice(1)}`;
|
|
121
|
+
}
|
|
122
|
+
static mapCyrillic(value) {
|
|
123
|
+
return value.split("").map((char) => this.cyrillicToLatin.get(char) || char).join("");
|
|
124
|
+
}
|
|
125
|
+
static mapLatin(value) {
|
|
126
|
+
const latinToCyrillic = new Map(Array.from(this.cyrillicToLatin).map(([cyr, lat]) => [lat, cyr]));
|
|
127
|
+
return value.split("").map((char) => latinToCyrillic.get(char) || char).join("");
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Validates the Ukrainian IBAN number by format and checksum.
|
|
131
|
+
*/
|
|
132
|
+
static isIbanNumberValid(iban) {
|
|
133
|
+
const CODE_LENGTHS = { UA: 29 };
|
|
134
|
+
const code = iban.match(/^([A-Z]{2})(\d{2})([\dA-Z]+)$/);
|
|
135
|
+
if (!code || iban.length !== CODE_LENGTHS[code[1]]) return false;
|
|
136
|
+
const digits = (code[3] + code[1] + code[2]).replaceAll(/[A-Z]/g, (letter) => {
|
|
137
|
+
const letterCode = letter.codePointAt(0);
|
|
138
|
+
return letterCode ? (letterCode - 55).toString() : NaN.toString();
|
|
139
|
+
});
|
|
140
|
+
return this.mod97(digits) === 1;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Extracts the bank code (МФО) from Ukrainian IBAN (4-10 digits). Does not validate the IBAN.
|
|
144
|
+
*
|
|
145
|
+
* @link https://en.wikipedia.org/wiki/International_Bank_Account_Number#IBAN_formats_by_country
|
|
146
|
+
* @example
|
|
147
|
+
* ```typescript
|
|
148
|
+
* const bankCode = extractBankCodeFromIban('UA833052991234567890123456789') // '305299'
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
static extractBankCodeFromIban(iban) {
|
|
152
|
+
return iban.slice(4, 10);
|
|
153
|
+
}
|
|
154
|
+
static getStreetName(street, streetType) {
|
|
155
|
+
if (!street) return "";
|
|
156
|
+
return streetType ? `${streetType} ${street}`.trim() : street;
|
|
157
|
+
}
|
|
158
|
+
static toHashedFilesWithSignatures(hashedFiles, signedItems) {
|
|
159
|
+
if (!hashedFiles?.length) throw new BadRequestError("Hashed files array is empty");
|
|
160
|
+
if (hashedFiles.length !== signedItems.length) throw new Error("Number of hashed files and signed elements are not matched");
|
|
161
|
+
const fileNames = new Set(hashedFiles.map(({ fileName }) => fileName));
|
|
162
|
+
const signatureByFilename = /* @__PURE__ */ new Map();
|
|
163
|
+
const signatures = [];
|
|
164
|
+
for (const { name, signature } of signedItems) {
|
|
165
|
+
if (!fileNames.has(name)) throw new Error(`Provided name doesn't match with a hashed file: ${name}`);
|
|
166
|
+
signatureByFilename.set(name, signature);
|
|
167
|
+
signatures.push(signature);
|
|
168
|
+
}
|
|
169
|
+
return [hashedFiles.map(({ fileName, fileHash }) => ({
|
|
170
|
+
name: fileName,
|
|
171
|
+
hash: fileHash,
|
|
172
|
+
signature: signatureByFilename.get(fileName)
|
|
173
|
+
})), signatures];
|
|
174
|
+
}
|
|
175
|
+
static handleError(err, cb) {
|
|
176
|
+
if (err instanceof ApiError) return cb(err);
|
|
177
|
+
if (err instanceof Error) return cb(this.toApiError(err));
|
|
178
|
+
const message = `Unexpected error caused: ${String(err)}`;
|
|
179
|
+
const wrappedError = new Error(message);
|
|
180
|
+
return cb(this.toApiError(wrappedError));
|
|
181
|
+
}
|
|
182
|
+
static makeSession(data) {
|
|
183
|
+
const { sessionType } = data;
|
|
184
|
+
switch (sessionType) {
|
|
185
|
+
case SessionType.User: return {
|
|
186
|
+
sessionType,
|
|
187
|
+
user: data
|
|
188
|
+
};
|
|
189
|
+
case SessionType.EResident: return {
|
|
190
|
+
sessionType,
|
|
191
|
+
user: data
|
|
192
|
+
};
|
|
193
|
+
case SessionType.EResidentApplicant: return {
|
|
194
|
+
sessionType,
|
|
195
|
+
user: data
|
|
196
|
+
};
|
|
197
|
+
case SessionType.Acquirer: return {
|
|
198
|
+
sessionType,
|
|
199
|
+
acquirer: data
|
|
200
|
+
};
|
|
201
|
+
case SessionType.PortalUser: return {
|
|
202
|
+
sessionType,
|
|
203
|
+
user: data
|
|
204
|
+
};
|
|
205
|
+
case SessionType.Partner: return {
|
|
206
|
+
sessionType,
|
|
207
|
+
partner: data
|
|
208
|
+
};
|
|
209
|
+
case SessionType.Temporary: return {
|
|
210
|
+
sessionType,
|
|
211
|
+
temporary: data
|
|
212
|
+
};
|
|
213
|
+
case SessionType.ServiceEntrance: return {
|
|
214
|
+
sessionType,
|
|
215
|
+
entrance: data
|
|
216
|
+
};
|
|
217
|
+
case SessionType.ServiceUser: return {
|
|
218
|
+
sessionType,
|
|
219
|
+
serviceUser: data
|
|
220
|
+
};
|
|
221
|
+
default: throw new TypeError(`Unhandled sessionType: ${String(sessionType)}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
static getActionNameWithVersion(actionName, actionVersion = ActionVersion.V0) {
|
|
225
|
+
return `${actionName}.${actionVersion}`;
|
|
226
|
+
}
|
|
227
|
+
static getGreeting(fName, skipEmogi) {
|
|
228
|
+
const text = `Вітаємо, ${fName}`;
|
|
229
|
+
return skipEmogi ? `${text}!` : `${text} 👋`;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Get age from birth date. Powered by {@link https://moment.github.io/luxon/#/ luxon}
|
|
233
|
+
*
|
|
234
|
+
* @param birthDay Input birth date.
|
|
235
|
+
* @param params Additional parameters. Default: `{ format: 'dd.MM.yyyy', unitOfTime: 'years' }`
|
|
236
|
+
* @param params.format Input date format (https://moment.github.io/luxon/#/formatting?id=table-of-tokens). Default - 'dd.MM.yyyy'
|
|
237
|
+
* @param params.unitOfTime Unit of time to get the difference in. Default - 'years'
|
|
238
|
+
* @param params.relativeTo Date to compare the input date to. Default - current date
|
|
239
|
+
*
|
|
240
|
+
* @returns Age in years if other `unitOfTime` is not specified
|
|
241
|
+
* @throws If input date is invalid
|
|
242
|
+
*/
|
|
243
|
+
static getAge(birthDay, params = {}) {
|
|
244
|
+
const { format = "dd.MM.yyyy", unitOfTime = "years", relativeTo } = params;
|
|
245
|
+
const birthdayDate = DateTime.fromFormat(birthDay, format);
|
|
246
|
+
if (!birthdayDate.isValid) throw new Error("Invalid user birthday");
|
|
247
|
+
return (relativeTo ? DateTime.fromISO(relativeTo) : DateTime.now()).diff(birthdayDate, [unitOfTime, "milliseconds"])[unitOfTime];
|
|
248
|
+
}
|
|
249
|
+
static getFullName(lastName, firstName, middleName, separator = " ") {
|
|
250
|
+
return [
|
|
251
|
+
lastName,
|
|
252
|
+
firstName,
|
|
253
|
+
middleName
|
|
254
|
+
].filter(Boolean).join(separator).trim();
|
|
255
|
+
}
|
|
256
|
+
static getShortName(lastName, firstName, middleName) {
|
|
257
|
+
const shortMiddleName = middleName ? ` ${middleName.charAt(0).toUpperCase()}.` : "";
|
|
258
|
+
return `${lastName} ${firstName.charAt(0).toUpperCase()}.${shortMiddleName}`.trim();
|
|
259
|
+
}
|
|
260
|
+
static getUserFullName(user) {
|
|
261
|
+
const { lName, fName, mName } = user;
|
|
262
|
+
return `${lName} ${fName} ${mName || ""}`.trim();
|
|
263
|
+
}
|
|
264
|
+
static getPassportFullName({ lastNameUA, firstNameUA, middleNameUA }) {
|
|
265
|
+
return `${lastNameUA} ${firstNameUA} ${middleNameUA || ""}`.trim();
|
|
266
|
+
}
|
|
267
|
+
static getChildFullName({ child: { lastName, firstName, middleName } }) {
|
|
268
|
+
return `${lastName} ${firstName} ${middleName || ""}`.trim();
|
|
269
|
+
}
|
|
270
|
+
static encodeObjectToBase64(object) {
|
|
271
|
+
return Buffer.from(JSON.stringify(object)).toString("base64");
|
|
272
|
+
}
|
|
273
|
+
static decodeObjectFromBase64(encodedObject) {
|
|
274
|
+
return JSON.parse(Buffer.from(encodedObject, "base64").toString());
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Format string or Date to the specified string format. Powered by {@link https://moment.github.io/luxon/#/ luxon}
|
|
278
|
+
*
|
|
279
|
+
* @param date Input date
|
|
280
|
+
* @param format Output date format (https://moment.github.io/luxon/#/formatting?id=table-of-tokens)
|
|
281
|
+
* @param fromFormat Input date format (https://moment.github.io/luxon/#/formatting?id=table-of-tokens). Used for string `date`. Default - 'dd.MM.yyyy'
|
|
282
|
+
*
|
|
283
|
+
* @throws If input date is invalid
|
|
284
|
+
*/
|
|
285
|
+
static formatDate(date, format, fromFormat = "dd.MM.yyyy") {
|
|
286
|
+
const convertedDate = date instanceof Date ? DateTime.fromJSDate(date) : DateTime.fromFormat(date, fromFormat);
|
|
287
|
+
const { isValid, invalidReason, invalidExplanation } = convertedDate;
|
|
288
|
+
if (!isValid) throw new Error(`Invalid date -> ${invalidReason}: ${invalidExplanation}`);
|
|
289
|
+
return convertedDate.setLocale("uk-UA").toFormat(format);
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Check if the input date is valid. Powered by {@link https://moment.github.io/luxon/#/ luxon}
|
|
293
|
+
*
|
|
294
|
+
* @param date Input date.
|
|
295
|
+
* @param format `date` format (https://moment.github.io/luxon/#/formatting?id=table-of-tokens). Default - 'yyyy-MM-dd'
|
|
296
|
+
*
|
|
297
|
+
* @throws If input date is invalid
|
|
298
|
+
*/
|
|
299
|
+
static validateDate(date, format = "yyyy-MM-dd") {
|
|
300
|
+
const parsedDate = DateTime.fromFormat(date, format);
|
|
301
|
+
if (!parsedDate.isValid) throw new BadRequestError(`Invalid date: ${parsedDate.invalidReason} ${parsedDate.invalidExplanation}`);
|
|
302
|
+
}
|
|
303
|
+
static formatPhoneNumber(rawPhone, separator = " ", prefix = "+") {
|
|
304
|
+
return prefix + rawPhone.replace(/^\+?(\d{2})(\d{3})(\d{3})(\d{2})(\d+)$/, [
|
|
305
|
+
"$1",
|
|
306
|
+
"$2",
|
|
307
|
+
"$3",
|
|
308
|
+
"$4",
|
|
309
|
+
"$5"
|
|
310
|
+
].join(separator));
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Format number as: "XX XXX,XX {units}".
|
|
314
|
+
*
|
|
315
|
+
* @param amount Input number
|
|
316
|
+
* @param units Unit to be append at the end of the formatted number
|
|
317
|
+
* @param minimumFractionDigits Minimum number of fraction digits
|
|
318
|
+
*/
|
|
319
|
+
static formatAmountWithThousandsSeparator(amount, units, minimumFractionDigits) {
|
|
320
|
+
const result = amount.toLocaleString("uk-UA", {
|
|
321
|
+
useGrouping: true,
|
|
322
|
+
minimumFractionDigits
|
|
323
|
+
}).replaceAll("\xA0", " ");
|
|
324
|
+
return units ? `${result} ${units}` : result;
|
|
325
|
+
}
|
|
326
|
+
static convertToPennies(amount) {
|
|
327
|
+
return this.toDecimalPlaces(amount * 100, 0);
|
|
328
|
+
}
|
|
329
|
+
static convertFromPennies(amount) {
|
|
330
|
+
return this.toDecimalPlaces(amount / 100, 2);
|
|
331
|
+
}
|
|
332
|
+
static multiplySum(amount, multiplier, decimalPlaces) {
|
|
333
|
+
const multipliedSum = amount * 100 * multiplier / 100;
|
|
334
|
+
return this.toDecimalPlaces(multipliedSum, decimalPlaces);
|
|
335
|
+
}
|
|
336
|
+
static toDecimalPlaces(amount, decimalPlaces = 2) {
|
|
337
|
+
return Number.parseFloat(amount.toFixed(decimalPlaces));
|
|
338
|
+
}
|
|
339
|
+
static filterByAppVersions(items, headers) {
|
|
340
|
+
const { appVersion, platformType } = headers || {};
|
|
341
|
+
if (!appVersion || !platformType) return items;
|
|
342
|
+
return items.filter(({ appVersions }) => {
|
|
343
|
+
if (!appVersions) return true;
|
|
344
|
+
return ApplicationUtils.isAppVersionMatch(appVersion, platformType, appVersions);
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
static isAppVersionMatch(appVersion, platformType, appVersions) {
|
|
348
|
+
if (appVersions.versions?.[platformType]?.some((version) => compare(appVersion, version, "="))) return true;
|
|
349
|
+
const minVersion = appVersions.minVersion?.[platformType];
|
|
350
|
+
const maxVersion = appVersions.maxVersion?.[platformType];
|
|
351
|
+
if (minVersion && maxVersion) {
|
|
352
|
+
if (compare(appVersion, minVersion, ">=") && compare(appVersion, maxVersion, "<=")) return true;
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
if (minVersion && compare(appVersion, minVersion, ">=")) return true;
|
|
356
|
+
if (maxVersion && compare(appVersion, maxVersion, "<=")) return true;
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
static getFileName(name, id, requestDateTime, postFix) {
|
|
360
|
+
return [
|
|
361
|
+
name.trim(),
|
|
362
|
+
id.trim(),
|
|
363
|
+
requestDateTime?.trim(),
|
|
364
|
+
postFix?.trim()
|
|
365
|
+
].filter(Boolean).join("-");
|
|
366
|
+
}
|
|
367
|
+
static sanitizeString(input) {
|
|
368
|
+
return input.replaceAll(/[^\da-zа-яєіїґ]/gi, "");
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Returns the appropriate plural form of a string based on the specified numeric value and locale.
|
|
372
|
+
* Uses the pluralization rules for the provided locale to determine the correct form among the options.
|
|
373
|
+
*
|
|
374
|
+
* @param value - The number used to determine the plural form.
|
|
375
|
+
* @param one - The form for singular (e.g., "one item").
|
|
376
|
+
* @param few - The form for a few items (e.g., "few items").
|
|
377
|
+
* @param many - The form for many items (e.g., "many items").
|
|
378
|
+
* @param other - Optional. The fallback form if none of the specific plural rules match.
|
|
379
|
+
* Defaults to the 'many' form if not provided.
|
|
380
|
+
* @param locale - The locale used for pluralization rules. Defaults to 'uk' (Ukrainian).
|
|
381
|
+
* @returns The appropriate pluralized string based on the value.
|
|
382
|
+
*
|
|
383
|
+
* @example
|
|
384
|
+
* getPluralForm(1, 'яблуко', 'яблука', 'яблук'); // 'яблуко'
|
|
385
|
+
* getPluralForm(2, 'яблуко', 'яблука', 'яблук'); // 'яблука'
|
|
386
|
+
* getPluralForm(0, 'яблуко', 'яблука', 'яблук', 'плоди'); // 'плоди'
|
|
387
|
+
* getPluralForm(0, 'яблуко', 'яблука', 'яблук'); // 'яблук' (uses 'many' as default fallback)
|
|
388
|
+
*/
|
|
389
|
+
static pluralizeString(value, one, few, many, other, locale = "uk") {
|
|
390
|
+
switch (new Intl.PluralRules(locale).select(value)) {
|
|
391
|
+
case "one": return one;
|
|
392
|
+
case "few": return few;
|
|
393
|
+
case "many": return many;
|
|
394
|
+
default: return other || many;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Returns the name of a service from a package.json in the current working directory (process.cwd()) converted to PascalCase.
|
|
399
|
+
* If the name is scoped, it will be returned without the scope.
|
|
400
|
+
* If the name is not found, an empty string is returned. Also, all whitespace characters are removed.
|
|
401
|
+
*
|
|
402
|
+
* @returns The name of the service in PascalCase.
|
|
403
|
+
*
|
|
404
|
+
* @example
|
|
405
|
+
* getServiceName() // 'my-service' -> 'MyService'
|
|
406
|
+
* getServiceName() // '@scope/my-service' -> 'MyService'
|
|
407
|
+
*/
|
|
408
|
+
static getServiceName() {
|
|
409
|
+
try {
|
|
410
|
+
const packageName = this.getPackageJson().name || "";
|
|
411
|
+
let nameWithoutScope = packageName;
|
|
412
|
+
if (packageName.startsWith("@")) {
|
|
413
|
+
const parts = packageName.split("/");
|
|
414
|
+
if (parts.length > 1) nameWithoutScope = parts[1];
|
|
415
|
+
}
|
|
416
|
+
return startCase(camelCase(nameWithoutScope)).replaceAll(/\s/g, "");
|
|
417
|
+
} catch {
|
|
418
|
+
return "";
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Returns the version of a service from a package.json in the current working directory (process.cwd()).
|
|
423
|
+
* If the version is not found, an empty string is returned.
|
|
424
|
+
*
|
|
425
|
+
* @returns The version of the service.
|
|
426
|
+
*
|
|
427
|
+
* @example
|
|
428
|
+
* getServiceVersion() // '1.143.0-rc.2'
|
|
429
|
+
*/
|
|
430
|
+
static getServiceVersion() {
|
|
431
|
+
try {
|
|
432
|
+
return this.getPackageJson().version || "";
|
|
433
|
+
} catch {
|
|
434
|
+
return "";
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Format string according to mask pattern
|
|
439
|
+
*
|
|
440
|
+
* Basic Examples:
|
|
441
|
+
* '12345' with '##-###' -> '12-345'
|
|
442
|
+
* 'AB345' with '##-###' -> 'AB-345'
|
|
443
|
+
* '123456' with '##.####' -> '12.3456'
|
|
444
|
+
* '12345' with '### ##' -> '123 45'
|
|
445
|
+
* 'ABCDEF' with '##-##-##' -> 'AB-CD-EF'
|
|
446
|
+
*/
|
|
447
|
+
static formatMask(rawData, mask) {
|
|
448
|
+
if (!rawData || !mask) return rawData;
|
|
449
|
+
let result = "";
|
|
450
|
+
let dataIndex = 0;
|
|
451
|
+
for (const element of mask) {
|
|
452
|
+
if (dataIndex >= rawData.length) break;
|
|
453
|
+
if (element === "#") {
|
|
454
|
+
result += rawData[dataIndex];
|
|
455
|
+
dataIndex++;
|
|
456
|
+
} else result += element;
|
|
457
|
+
}
|
|
458
|
+
result += rawData.slice(dataIndex);
|
|
459
|
+
return result;
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Memoize a function.
|
|
463
|
+
* The function is memoized by the arguments (`JSON.stringify(args)`) and the result is cached for the specified time to live in milliseconds.
|
|
464
|
+
* If the function is called with the same arguments again, the cached result is returned.
|
|
465
|
+
* If the function is called with the same arguments again after the time to live has expired, the function is called again and the result is cached again.
|
|
466
|
+
*
|
|
467
|
+
* @param fn - The function to memoize.
|
|
468
|
+
* @param ttl - The time to live for the cached value in milliseconds.
|
|
469
|
+
* @returns The memoized function.
|
|
470
|
+
*/
|
|
471
|
+
static memoize(fn, ttl = Infinity) {
|
|
472
|
+
const promiseCache = /* @__PURE__ */ new Map();
|
|
473
|
+
const cache = /* @__PURE__ */ new Map();
|
|
474
|
+
mapAgeCleaner(cache);
|
|
475
|
+
const memoized = (async (...args) => {
|
|
476
|
+
const key = JSON.stringify(args);
|
|
477
|
+
const cachedPromise = promiseCache.get(key);
|
|
478
|
+
if (cachedPromise) return cachedPromise;
|
|
479
|
+
const cachedValue = cache.get(key);
|
|
480
|
+
if (cachedValue) return cachedValue.data;
|
|
481
|
+
try {
|
|
482
|
+
const promise = fn(...args);
|
|
483
|
+
promiseCache.set(key, promise);
|
|
484
|
+
const result = await promise;
|
|
485
|
+
cache.set(key, {
|
|
486
|
+
data: result,
|
|
487
|
+
maxAge: Date.now() + ttl
|
|
488
|
+
});
|
|
489
|
+
return result;
|
|
490
|
+
} finally {
|
|
491
|
+
promiseCache.delete(key);
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
return memoized;
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Recursively encodes all string values in an object or string using encodeURIComponent.
|
|
498
|
+
* For objects, it creates a deep clone and processes all string values.
|
|
499
|
+
* For strings, it directly applies the encoding.
|
|
500
|
+
*
|
|
501
|
+
* @param data Input data to encode - can be an object or string
|
|
502
|
+
* @returns Deep cloned object with encoded string values, or encoded string if input was string
|
|
503
|
+
* @example
|
|
504
|
+
* ```typescript
|
|
505
|
+
* const data = { name: 'John Doe', email: 'john@example.com' }
|
|
506
|
+
* const encoded = ApplicationUtils.encodeValuesWithIterator(data)
|
|
507
|
+
* // Result: { name: 'John%20Doe', email: 'john%40example.com' }
|
|
508
|
+
* ```
|
|
509
|
+
*/
|
|
510
|
+
static encodeValuesWithIterator(data) {
|
|
511
|
+
return this.processValues(data, encodeURIComponent);
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Recursively decodes all string values in an object or string using decodeURIComponent.
|
|
515
|
+
* For objects, it creates a deep clone and processes all string values.
|
|
516
|
+
* For strings, it directly applies the decoding.
|
|
517
|
+
*
|
|
518
|
+
* @param data Input data to decode - can be an object or string
|
|
519
|
+
* @returns Deep cloned object with decoded string values, or decoded string if input was string
|
|
520
|
+
* @example
|
|
521
|
+
* ```typescript
|
|
522
|
+
* const data = { name: 'John%20Doe', email: 'john%40example.com' }
|
|
523
|
+
* const decoded = ApplicationUtils.decodeValuesWithIterator(data)
|
|
524
|
+
* // Result: { name: 'John Doe', email: 'john@example.com' }
|
|
525
|
+
* ```
|
|
526
|
+
*/
|
|
527
|
+
static decodeValuesWithIterator(data) {
|
|
528
|
+
return this.processValues(data, decodeURIComponent);
|
|
529
|
+
}
|
|
530
|
+
static mod97(string) {
|
|
531
|
+
let checksum = string.slice(0, 2);
|
|
532
|
+
let fragment;
|
|
533
|
+
for (let offset = 2; offset < string.length; offset += 7) {
|
|
534
|
+
fragment = String(checksum) + string.slice(offset, offset + 7);
|
|
535
|
+
checksum = Number.parseInt(fragment, 10) % 97;
|
|
536
|
+
}
|
|
537
|
+
return checksum;
|
|
538
|
+
}
|
|
539
|
+
static toApiError(err) {
|
|
540
|
+
const { type, code, message, data = {}, keyValue } = err;
|
|
541
|
+
const processCode = data.processCode;
|
|
542
|
+
if (code === 11e3 && keyValue) data.keyValue = keyValue;
|
|
543
|
+
return new ApiError(message, code || HttpStatusCode.INTERNAL_SERVER_ERROR, data, processCode, type);
|
|
544
|
+
}
|
|
545
|
+
static getPackageJson() {
|
|
546
|
+
const packageJsonPath = path.resolve(process.cwd(), "package.json");
|
|
547
|
+
return JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
548
|
+
}
|
|
549
|
+
static processObject(obj, processor) {
|
|
550
|
+
if (typeof obj === "string") try {
|
|
551
|
+
return processor(obj);
|
|
552
|
+
} catch {
|
|
553
|
+
return obj;
|
|
554
|
+
}
|
|
555
|
+
if (Array.isArray(obj)) return obj.map((item) => {
|
|
556
|
+
return this.processObject(item, processor);
|
|
557
|
+
});
|
|
558
|
+
if (obj && typeof obj === "object") {
|
|
559
|
+
if (obj instanceof Date || obj instanceof RegExp || obj instanceof Map || obj instanceof Set || obj instanceof Error || obj instanceof URL || obj instanceof URLSearchParams) return obj;
|
|
560
|
+
const result = {};
|
|
561
|
+
for (const [key, value] of Object.entries(obj)) result[key] = this.processObject(value, processor);
|
|
562
|
+
return result;
|
|
563
|
+
}
|
|
564
|
+
return obj;
|
|
565
|
+
}
|
|
566
|
+
static processValues(data, processor) {
|
|
567
|
+
if (typeof data === "string") try {
|
|
568
|
+
return processor(data);
|
|
569
|
+
} catch {
|
|
570
|
+
return data;
|
|
571
|
+
}
|
|
572
|
+
const clonedData = cloneDeep(data);
|
|
573
|
+
return this.processObject(clonedData, processor);
|
|
574
|
+
}
|
|
4
575
|
};
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
-
const compare_versions_1 = require("compare-versions");
|
|
10
|
-
const lodash_1 = require("lodash");
|
|
11
|
-
const luxon_1 = require("luxon");
|
|
12
|
-
const map_age_cleaner_1 = __importDefault(require("map-age-cleaner"));
|
|
13
|
-
const errors_1 = require("@diia-inhouse/errors");
|
|
14
|
-
const types_1 = require("@diia-inhouse/types");
|
|
15
|
-
class ApplicationUtils {
|
|
16
|
-
static documentTypeToCamelCaseExceptions = {
|
|
17
|
-
['student-id-card']: 'studentCard',
|
|
18
|
-
['internal-passport']: 'idCard',
|
|
19
|
-
};
|
|
20
|
-
static camelCaseToDocumentTypeExceptions = {
|
|
21
|
-
['studentCard']: 'student-id-card',
|
|
22
|
-
['idCard']: 'internal-passport',
|
|
23
|
-
};
|
|
24
|
-
static defaultItnDate = '31.12.1899';
|
|
25
|
-
static defaultFormat = 'dd.MM.yyyy';
|
|
26
|
-
static cyrillicToLatin = new Map([
|
|
27
|
-
['і', 'i'],
|
|
28
|
-
['е', 'e'],
|
|
29
|
-
['у', 'y'],
|
|
30
|
-
['и', 'u'],
|
|
31
|
-
['о', 'o'],
|
|
32
|
-
['р', 'p'],
|
|
33
|
-
['а', 'a'],
|
|
34
|
-
['к', 'k'],
|
|
35
|
-
['х', 'x'],
|
|
36
|
-
['с', 'c'],
|
|
37
|
-
['І', 'I'],
|
|
38
|
-
['Е', 'E'],
|
|
39
|
-
['Т', 'T'],
|
|
40
|
-
['О', 'O'],
|
|
41
|
-
['Р', 'P'],
|
|
42
|
-
['А', 'A'],
|
|
43
|
-
['Н', 'H'],
|
|
44
|
-
['К', 'K'],
|
|
45
|
-
['Х', 'X'],
|
|
46
|
-
['С', 'C'],
|
|
47
|
-
['В', 'B'],
|
|
48
|
-
['М', 'M'],
|
|
49
|
-
['У', 'Y'],
|
|
50
|
-
]);
|
|
51
|
-
static nameComponentsToLowerCase = [
|
|
52
|
-
'огли',
|
|
53
|
-
'оглу',
|
|
54
|
-
'заде',
|
|
55
|
-
'кизи',
|
|
56
|
-
'бей',
|
|
57
|
-
'бек',
|
|
58
|
-
'паша',
|
|
59
|
-
'хан',
|
|
60
|
-
'шах',
|
|
61
|
-
'мелік',
|
|
62
|
-
'зуль',
|
|
63
|
-
'ібн',
|
|
64
|
-
'тер',
|
|
65
|
-
'аль',
|
|
66
|
-
'ель',
|
|
67
|
-
'аш',
|
|
68
|
-
'ас',
|
|
69
|
-
'ад',
|
|
70
|
-
'ель',
|
|
71
|
-
'ес',
|
|
72
|
-
];
|
|
73
|
-
static documentTypeToCamelCase(documentType) {
|
|
74
|
-
return ApplicationUtils.documentTypeToCamelCaseExceptions[documentType] || (0, lodash_1.camelCase)(documentType);
|
|
75
|
-
}
|
|
76
|
-
static camelCaseToDocumentType(camelCaseDocumentType) {
|
|
77
|
-
return ApplicationUtils.camelCaseToDocumentTypeExceptions[camelCaseDocumentType] || (0, lodash_1.kebabCase)(camelCaseDocumentType);
|
|
78
|
-
}
|
|
79
|
-
static isItnChecksumValid(itn) {
|
|
80
|
-
if (itn.length !== 10) {
|
|
81
|
-
return false;
|
|
82
|
-
}
|
|
83
|
-
const ITN_POLY = [-1, 5, 7, 9, 4, 6, 10, 5, 7];
|
|
84
|
-
const digits = [...itn].map(Number);
|
|
85
|
-
const checksum = ITN_POLY.reduce((a, x, i) => a + x * digits[i], 0);
|
|
86
|
-
const controlDigit = (checksum - 11 * Math.floor(checksum / 11)) % 10;
|
|
87
|
-
const last = digits.at(-1);
|
|
88
|
-
return last === controlDigit;
|
|
89
|
-
}
|
|
90
|
-
static getBirthDayFromItn(itn) {
|
|
91
|
-
if (itn.length !== 10) {
|
|
92
|
-
return '';
|
|
93
|
-
}
|
|
94
|
-
const days = Number.parseInt(itn.slice(0, 5), 10);
|
|
95
|
-
return luxon_1.DateTime.fromFormat(this.defaultItnDate, this.defaultFormat).plus({ days }).toFormat(this.defaultFormat);
|
|
96
|
-
}
|
|
97
|
-
static getGenderFromItn(itn) {
|
|
98
|
-
return Number(itn.at(-2)) % 2 === 0 ? types_1.Gender.female : types_1.Gender.male;
|
|
99
|
-
}
|
|
100
|
-
static isItnFormatValid(itn) {
|
|
101
|
-
if (/^0{1,20}$/.test(itn) || itn === 'null' || !/^\d{10}$/.test(itn)) {
|
|
102
|
-
return false;
|
|
103
|
-
}
|
|
104
|
-
return true;
|
|
105
|
-
}
|
|
106
|
-
static capitalizeName(name) {
|
|
107
|
-
if (!name) {
|
|
108
|
-
return '';
|
|
109
|
-
}
|
|
110
|
-
const loweredNameParts = name.toLowerCase().split(' ');
|
|
111
|
-
if (loweredNameParts.length === 1) {
|
|
112
|
-
const [namePart] = loweredNameParts;
|
|
113
|
-
if (this.nameComponentsToLowerCase.includes(namePart)) {
|
|
114
|
-
return this.capitalizeFirstLetter(namePart);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
return loweredNameParts
|
|
118
|
-
.map((word) => word
|
|
119
|
-
.split('-')
|
|
120
|
-
.map((namePart) => this.nameComponentsToLowerCase.includes(namePart) ? namePart : this.capitalizeFirstLetter(namePart))
|
|
121
|
-
.join('-'))
|
|
122
|
-
.join(' ');
|
|
123
|
-
}
|
|
124
|
-
static capitalizeFirstLetter(str) {
|
|
125
|
-
if (typeof str !== 'string') {
|
|
126
|
-
return '';
|
|
127
|
-
}
|
|
128
|
-
return `${str.charAt(0).toUpperCase()}${str.slice(1)}`;
|
|
129
|
-
}
|
|
130
|
-
static lowerFirstLetter(str) {
|
|
131
|
-
if (typeof str !== 'string') {
|
|
132
|
-
return '';
|
|
133
|
-
}
|
|
134
|
-
return `${str.charAt(0).toLowerCase()}${str.slice(1)}`;
|
|
135
|
-
}
|
|
136
|
-
static mapCyrillic(value) {
|
|
137
|
-
return value
|
|
138
|
-
.split('')
|
|
139
|
-
.map((char) => this.cyrillicToLatin.get(char) || char)
|
|
140
|
-
.join('');
|
|
141
|
-
}
|
|
142
|
-
static mapLatin(value) {
|
|
143
|
-
const latinToCyrillic = new Map(Array.from(this.cyrillicToLatin).map(([cyr, lat]) => [lat, cyr]));
|
|
144
|
-
return value
|
|
145
|
-
.split('')
|
|
146
|
-
.map((char) => latinToCyrillic.get(char) || char)
|
|
147
|
-
.join('');
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* Validates the Ukrainian IBAN number by format and checksum.
|
|
151
|
-
*/
|
|
152
|
-
static isIbanNumberValid(iban) {
|
|
153
|
-
const CODE_LENGTHS = {
|
|
154
|
-
UA: 29,
|
|
155
|
-
};
|
|
156
|
-
const code = iban.match(/^([A-Z]{2})(\d{2})([\dA-Z]+)$/);
|
|
157
|
-
if (!code || iban.length !== CODE_LENGTHS[code[1]]) {
|
|
158
|
-
return false;
|
|
159
|
-
}
|
|
160
|
-
const digits = (code[3] + code[1] + code[2]).replaceAll(/[A-Z]/g, (letter) => {
|
|
161
|
-
const letterCode = letter.codePointAt(0);
|
|
162
|
-
return letterCode ? (letterCode - 55).toString() : Number.NaN.toString();
|
|
163
|
-
});
|
|
164
|
-
return this.mod97(digits) === 1;
|
|
165
|
-
}
|
|
166
|
-
/**
|
|
167
|
-
* Extracts the bank code (МФО) from Ukrainian IBAN (4-10 digits). Does not validate the IBAN.
|
|
168
|
-
*
|
|
169
|
-
* @link https://en.wikipedia.org/wiki/International_Bank_Account_Number#IBAN_formats_by_country
|
|
170
|
-
* @example
|
|
171
|
-
* ```typescript
|
|
172
|
-
* const bankCode = extractBankCodeFromIban('UA833052991234567890123456789') // '305299'
|
|
173
|
-
* ```
|
|
174
|
-
*/
|
|
175
|
-
static extractBankCodeFromIban(iban) {
|
|
176
|
-
return iban.slice(4, 10);
|
|
177
|
-
}
|
|
178
|
-
static getStreetName(street, streetType) {
|
|
179
|
-
if (!street) {
|
|
180
|
-
return '';
|
|
181
|
-
}
|
|
182
|
-
return streetType ? `${streetType} ${street}`.trim() : street;
|
|
183
|
-
}
|
|
184
|
-
static toHashedFilesWithSignatures(hashedFiles, signedItems) {
|
|
185
|
-
if (!hashedFiles?.length) {
|
|
186
|
-
throw new errors_1.BadRequestError('Hashed files array is empty');
|
|
187
|
-
}
|
|
188
|
-
if (hashedFiles.length !== signedItems.length) {
|
|
189
|
-
throw new Error('Number of hashed files and signed elements are not matched');
|
|
190
|
-
}
|
|
191
|
-
const fileNames = new Set(hashedFiles.map(({ fileName }) => fileName));
|
|
192
|
-
const signatureByFilename = new Map();
|
|
193
|
-
const signatures = [];
|
|
194
|
-
for (const { name, signature } of signedItems) {
|
|
195
|
-
if (!fileNames.has(name)) {
|
|
196
|
-
throw new Error(`Provided name doesn't match with a hashed file: ${name}`);
|
|
197
|
-
}
|
|
198
|
-
signatureByFilename.set(name, signature);
|
|
199
|
-
signatures.push(signature);
|
|
200
|
-
}
|
|
201
|
-
const hashedFilesWithSignatures = hashedFiles.map(({ fileName, fileHash }) => ({
|
|
202
|
-
name: fileName,
|
|
203
|
-
hash: fileHash,
|
|
204
|
-
signature: signatureByFilename.get(fileName),
|
|
205
|
-
}));
|
|
206
|
-
return [hashedFilesWithSignatures, signatures];
|
|
207
|
-
}
|
|
208
|
-
static handleError(err, cb) {
|
|
209
|
-
if (err instanceof errors_1.ApiError) {
|
|
210
|
-
return cb(err);
|
|
211
|
-
}
|
|
212
|
-
if (err instanceof Error) {
|
|
213
|
-
return cb(this.toApiError(err));
|
|
214
|
-
}
|
|
215
|
-
const message = `Unexpected error caused: ${err}`;
|
|
216
|
-
const wrappedError = new Error(message);
|
|
217
|
-
return cb(this.toApiError(wrappedError));
|
|
218
|
-
}
|
|
219
|
-
static makeSession(data) {
|
|
220
|
-
const { sessionType } = data;
|
|
221
|
-
switch (sessionType) {
|
|
222
|
-
case types_1.SessionType.User: {
|
|
223
|
-
return { sessionType, user: data };
|
|
224
|
-
}
|
|
225
|
-
case types_1.SessionType.EResident: {
|
|
226
|
-
return { sessionType, user: data };
|
|
227
|
-
}
|
|
228
|
-
case types_1.SessionType.EResidentApplicant: {
|
|
229
|
-
return { sessionType, user: data };
|
|
230
|
-
}
|
|
231
|
-
case types_1.SessionType.Acquirer: {
|
|
232
|
-
return { sessionType, acquirer: data };
|
|
233
|
-
}
|
|
234
|
-
case types_1.SessionType.PortalUser: {
|
|
235
|
-
return { sessionType, user: data };
|
|
236
|
-
}
|
|
237
|
-
case types_1.SessionType.Partner: {
|
|
238
|
-
return { sessionType, partner: data };
|
|
239
|
-
}
|
|
240
|
-
case types_1.SessionType.Temporary: {
|
|
241
|
-
return { sessionType, temporary: data };
|
|
242
|
-
}
|
|
243
|
-
case types_1.SessionType.ServiceEntrance: {
|
|
244
|
-
return { sessionType, entrance: data };
|
|
245
|
-
}
|
|
246
|
-
case types_1.SessionType.ServiceUser: {
|
|
247
|
-
return { sessionType, serviceUser: data };
|
|
248
|
-
}
|
|
249
|
-
default: {
|
|
250
|
-
const unhandledType = sessionType;
|
|
251
|
-
throw new TypeError(`Unhandled sessionType: ${unhandledType}`);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
static getActionNameWithVersion(actionName, actionVersion = types_1.ActionVersion.V0) {
|
|
256
|
-
return `${actionName}.${actionVersion}`;
|
|
257
|
-
}
|
|
258
|
-
static getGreeting(fName, skipEmogi) {
|
|
259
|
-
const text = `Вітаємо, ${fName}`;
|
|
260
|
-
return skipEmogi ? `${text}!` : `${text} 👋`;
|
|
261
|
-
}
|
|
262
|
-
/**
|
|
263
|
-
* Get age from birth date. Powered by {@link https://moment.github.io/luxon/#/ luxon}
|
|
264
|
-
*
|
|
265
|
-
* @param birthDay Input birth date.
|
|
266
|
-
* @param params Additional parameters. Default: `{ format: 'dd.MM.yyyy', unitOfTime: 'years' }`
|
|
267
|
-
* @param params.format Input date format (https://moment.github.io/luxon/#/formatting?id=table-of-tokens). Default - 'dd.MM.yyyy'
|
|
268
|
-
* @param params.unitOfTime Unit of time to get the difference in. Default - 'years'
|
|
269
|
-
* @param params.relativeTo Date to compare the input date to. Default - current date
|
|
270
|
-
*
|
|
271
|
-
* @returns Age in years if other `unitOfTime` is not specified
|
|
272
|
-
* @throws If input date is invalid
|
|
273
|
-
*/
|
|
274
|
-
static getAge(birthDay, params = {}) {
|
|
275
|
-
const { format = 'dd.MM.yyyy', unitOfTime = 'years', relativeTo } = params;
|
|
276
|
-
const birthdayDate = luxon_1.DateTime.fromFormat(birthDay, format);
|
|
277
|
-
if (!birthdayDate.isValid) {
|
|
278
|
-
throw new Error('Invalid user birthday');
|
|
279
|
-
}
|
|
280
|
-
const fromDate = relativeTo ? luxon_1.DateTime.fromISO(relativeTo) : luxon_1.DateTime.now();
|
|
281
|
-
/* Added an array of "units" with milliseconds to ensure that all larger unit values are integers.
|
|
282
|
-
For instance:
|
|
283
|
-
birthDay = '24.03.2009'
|
|
284
|
-
currentDate = '24.03.2023'
|
|
285
|
-
DateTime.now().diff(birthdayDate, 'years')['years'] => 14.001228454102156
|
|
286
|
-
// fix
|
|
287
|
-
DateTime.now().diff(birthdayDate, ['years', 'milliseconds'])['years'] => 14
|
|
288
|
-
DateTime.now().diff(birthdayDate, ['months', 'milliseconds'])['months'] => 168
|
|
289
|
-
*/
|
|
290
|
-
return fromDate.diff(birthdayDate, [unitOfTime, 'milliseconds'])[unitOfTime];
|
|
291
|
-
}
|
|
292
|
-
static getFullName(lastName, firstName, middleName, separator = ' ') {
|
|
293
|
-
return [lastName, firstName, middleName].filter(Boolean).join(separator).trim();
|
|
294
|
-
}
|
|
295
|
-
static getShortName(lastName, firstName, middleName) {
|
|
296
|
-
const shortMiddleName = middleName ? ` ${middleName.charAt(0).toUpperCase()}.` : '';
|
|
297
|
-
return `${lastName} ${firstName.charAt(0).toUpperCase()}.${shortMiddleName}`.trim();
|
|
298
|
-
}
|
|
299
|
-
static getUserFullName(user) {
|
|
300
|
-
const { lName, fName, mName } = user;
|
|
301
|
-
return `${lName} ${fName} ${mName || ''}`.trim();
|
|
302
|
-
}
|
|
303
|
-
static getPassportFullName({ lastNameUA, firstNameUA, middleNameUA, }) {
|
|
304
|
-
return `${lastNameUA} ${firstNameUA} ${middleNameUA || ''}`.trim();
|
|
305
|
-
}
|
|
306
|
-
static getChildFullName({ child: { lastName, firstName, middleName }, }) {
|
|
307
|
-
return `${lastName} ${firstName} ${middleName || ''}`.trim();
|
|
308
|
-
}
|
|
309
|
-
static encodeObjectToBase64(object) {
|
|
310
|
-
return Buffer.from(JSON.stringify(object)).toString('base64');
|
|
311
|
-
}
|
|
312
|
-
static decodeObjectFromBase64(encodedObject) {
|
|
313
|
-
return JSON.parse(Buffer.from(encodedObject, 'base64').toString());
|
|
314
|
-
}
|
|
315
|
-
/**
|
|
316
|
-
* Format string or Date to the specified string format. Powered by {@link https://moment.github.io/luxon/#/ luxon}
|
|
317
|
-
*
|
|
318
|
-
* @param date Input date
|
|
319
|
-
* @param format Output date format (https://moment.github.io/luxon/#/formatting?id=table-of-tokens)
|
|
320
|
-
* @param fromFormat Input date format (https://moment.github.io/luxon/#/formatting?id=table-of-tokens). Used for string `date`. Default - 'dd.MM.yyyy'
|
|
321
|
-
*
|
|
322
|
-
* @throws If input date is invalid
|
|
323
|
-
*/
|
|
324
|
-
static formatDate(date, format, fromFormat = 'dd.MM.yyyy') {
|
|
325
|
-
const convertedDate = date instanceof Date ? luxon_1.DateTime.fromJSDate(date) : luxon_1.DateTime.fromFormat(date, fromFormat);
|
|
326
|
-
const { isValid, invalidReason, invalidExplanation } = convertedDate;
|
|
327
|
-
if (!isValid) {
|
|
328
|
-
throw new Error(`Invalid date -> ${invalidReason}: ${invalidExplanation}`);
|
|
329
|
-
}
|
|
330
|
-
return convertedDate.setLocale('uk-UA').toFormat(format);
|
|
331
|
-
}
|
|
332
|
-
/**
|
|
333
|
-
* Check if the input date is valid. Powered by {@link https://moment.github.io/luxon/#/ luxon}
|
|
334
|
-
*
|
|
335
|
-
* @param date Input date.
|
|
336
|
-
* @param format `date` format (https://moment.github.io/luxon/#/formatting?id=table-of-tokens). Default - 'yyyy-MM-dd'
|
|
337
|
-
*
|
|
338
|
-
* @throws If input date is invalid
|
|
339
|
-
*/
|
|
340
|
-
static validateDate(date, format = 'yyyy-MM-dd') {
|
|
341
|
-
const parsedDate = luxon_1.DateTime.fromFormat(date, format);
|
|
342
|
-
if (!parsedDate.isValid) {
|
|
343
|
-
throw new errors_1.BadRequestError(`Invalid date: ${parsedDate.invalidReason} ${parsedDate.invalidExplanation}`);
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
static formatPhoneNumber(rawPhone, separator = ' ', prefix = '+') {
|
|
347
|
-
return prefix + rawPhone.replace(/^\+?(\d{2})(\d{3})(\d{3})(\d{2})(\d+)$/, ['$1', '$2', '$3', '$4', '$5'].join(separator));
|
|
348
|
-
}
|
|
349
|
-
/**
|
|
350
|
-
* Format number as: "XX XXX,XX {units}".
|
|
351
|
-
*
|
|
352
|
-
* @param amount Input number
|
|
353
|
-
* @param units Unit to be append at the end of the formatted number
|
|
354
|
-
* @param minimumFractionDigits Minimum number of fraction digits
|
|
355
|
-
*/
|
|
356
|
-
static formatAmountWithThousandsSeparator(amount, units, minimumFractionDigits) {
|
|
357
|
-
// https://github.com/nodejs/help/issues/4068 (char "\u00A0" not equals with space in test asserts; reproduced in node 18.13, 18.15, 18.17)
|
|
358
|
-
const result = amount.toLocaleString('uk-UA', { useGrouping: true, minimumFractionDigits }).replaceAll('\u00A0', ' ');
|
|
359
|
-
return units ? `${result} ${units}` : result;
|
|
360
|
-
}
|
|
361
|
-
static convertToPennies(amount) {
|
|
362
|
-
return this.toDecimalPlaces(amount * 100, 0);
|
|
363
|
-
}
|
|
364
|
-
static convertFromPennies(amount) {
|
|
365
|
-
return this.toDecimalPlaces(amount / 100, 2);
|
|
366
|
-
}
|
|
367
|
-
static multiplySum(amount, multiplier, decimalPlaces) {
|
|
368
|
-
const multipliedSum = (amount * 100 * multiplier) / 100;
|
|
369
|
-
return this.toDecimalPlaces(multipliedSum, decimalPlaces);
|
|
370
|
-
}
|
|
371
|
-
static toDecimalPlaces(amount, decimalPlaces = 2) {
|
|
372
|
-
return Number.parseFloat(amount.toFixed(decimalPlaces));
|
|
373
|
-
}
|
|
374
|
-
static filterByAppVersions(items, headers) {
|
|
375
|
-
const { appVersion, platformType } = headers || {};
|
|
376
|
-
if (!appVersion || !platformType) {
|
|
377
|
-
return items;
|
|
378
|
-
}
|
|
379
|
-
return items.filter(({ appVersions }) => {
|
|
380
|
-
if (!appVersions) {
|
|
381
|
-
return true;
|
|
382
|
-
}
|
|
383
|
-
return ApplicationUtils.isAppVersionMatch(appVersion, platformType, appVersions);
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
static isAppVersionMatch(appVersion, platformType, appVersions) {
|
|
387
|
-
if (appVersions.versions?.[platformType]?.some((version) => (0, compare_versions_1.compare)(appVersion, version, '='))) {
|
|
388
|
-
return true;
|
|
389
|
-
}
|
|
390
|
-
const minVersion = appVersions.minVersion?.[platformType];
|
|
391
|
-
const maxVersion = appVersions.maxVersion?.[platformType];
|
|
392
|
-
if (minVersion && maxVersion) {
|
|
393
|
-
if ((0, compare_versions_1.compare)(appVersion, minVersion, '>=') && (0, compare_versions_1.compare)(appVersion, maxVersion, '<=')) {
|
|
394
|
-
return true;
|
|
395
|
-
}
|
|
396
|
-
return false;
|
|
397
|
-
}
|
|
398
|
-
if (minVersion && (0, compare_versions_1.compare)(appVersion, minVersion, '>=')) {
|
|
399
|
-
return true;
|
|
400
|
-
}
|
|
401
|
-
if (maxVersion && (0, compare_versions_1.compare)(appVersion, maxVersion, '<=')) {
|
|
402
|
-
return true;
|
|
403
|
-
}
|
|
404
|
-
return false;
|
|
405
|
-
}
|
|
406
|
-
static getFileName(name, id, requestDateTime, postFix) {
|
|
407
|
-
return [name.trim(), id.trim(), requestDateTime?.trim(), postFix?.trim()].filter(Boolean).join('-');
|
|
408
|
-
}
|
|
409
|
-
static sanitizeString(input) {
|
|
410
|
-
return input.replaceAll(/[^\da-zа-яєіїґ]/gi, '');
|
|
411
|
-
}
|
|
412
|
-
/**
|
|
413
|
-
* Returns the appropriate plural form of a string based on the specified numeric value and locale.
|
|
414
|
-
* Uses the pluralization rules for the provided locale to determine the correct form among the options.
|
|
415
|
-
*
|
|
416
|
-
* @param value - The number used to determine the plural form.
|
|
417
|
-
* @param one - The form for singular (e.g., "one item").
|
|
418
|
-
* @param few - The form for a few items (e.g., "few items").
|
|
419
|
-
* @param many - The form for many items (e.g., "many items").
|
|
420
|
-
* @param other - Optional. The fallback form if none of the specific plural rules match.
|
|
421
|
-
* Defaults to the 'many' form if not provided.
|
|
422
|
-
* @param locale - The locale used for pluralization rules. Defaults to 'uk' (Ukrainian).
|
|
423
|
-
* @returns The appropriate pluralized string based on the value.
|
|
424
|
-
*
|
|
425
|
-
* @example
|
|
426
|
-
* getPluralForm(1, 'яблуко', 'яблука', 'яблук'); // 'яблуко'
|
|
427
|
-
* getPluralForm(2, 'яблуко', 'яблука', 'яблук'); // 'яблука'
|
|
428
|
-
* getPluralForm(0, 'яблуко', 'яблука', 'яблук', 'плоди'); // 'плоди'
|
|
429
|
-
* getPluralForm(0, 'яблуко', 'яблука', 'яблук'); // 'яблук' (uses 'many' as default fallback)
|
|
430
|
-
*/
|
|
431
|
-
static pluralizeString(value, one, few, many, other, locale = 'uk') {
|
|
432
|
-
const rules = new Intl.PluralRules(locale);
|
|
433
|
-
switch (rules.select(value)) {
|
|
434
|
-
case 'one': {
|
|
435
|
-
return one;
|
|
436
|
-
}
|
|
437
|
-
case 'few': {
|
|
438
|
-
return few;
|
|
439
|
-
}
|
|
440
|
-
case 'many': {
|
|
441
|
-
return many;
|
|
442
|
-
}
|
|
443
|
-
default: {
|
|
444
|
-
return other || many;
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
/**
|
|
449
|
-
* Returns the name of a service from a package.json in the current working directory (process.cwd()) converted to PascalCase.
|
|
450
|
-
* If the name is scoped, it will be returned without the scope.
|
|
451
|
-
* If the name is not found, an empty string is returned. Also, all whitespace characters are removed.
|
|
452
|
-
*
|
|
453
|
-
* @returns The name of the service in PascalCase.
|
|
454
|
-
*
|
|
455
|
-
* @example
|
|
456
|
-
* getServiceName() // 'my-service' -> 'MyService'
|
|
457
|
-
* getServiceName() // '@scope/my-service' -> 'MyService'
|
|
458
|
-
*/
|
|
459
|
-
static getServiceName() {
|
|
460
|
-
try {
|
|
461
|
-
const packageName = this.getPackageJson().name || '';
|
|
462
|
-
let nameWithoutScope = packageName;
|
|
463
|
-
if (packageName.startsWith('@')) {
|
|
464
|
-
const parts = packageName.split('/');
|
|
465
|
-
if (parts.length > 1) {
|
|
466
|
-
nameWithoutScope = parts[1];
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
return (0, lodash_1.startCase)((0, lodash_1.camelCase)(nameWithoutScope)).replaceAll(/\s/g, '');
|
|
470
|
-
}
|
|
471
|
-
catch {
|
|
472
|
-
return '';
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
/**
|
|
476
|
-
* Returns the version of a service from a package.json in the current working directory (process.cwd()).
|
|
477
|
-
* If the version is not found, an empty string is returned.
|
|
478
|
-
*
|
|
479
|
-
* @returns The version of the service.
|
|
480
|
-
*
|
|
481
|
-
* @example
|
|
482
|
-
* getServiceVersion() // '1.143.0-rc.2'
|
|
483
|
-
*/
|
|
484
|
-
static getServiceVersion() {
|
|
485
|
-
try {
|
|
486
|
-
const packageJson = this.getPackageJson();
|
|
487
|
-
return packageJson.version || '';
|
|
488
|
-
}
|
|
489
|
-
catch {
|
|
490
|
-
return '';
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
/**
|
|
494
|
-
* Format string according to mask pattern
|
|
495
|
-
*
|
|
496
|
-
* Basic Examples:
|
|
497
|
-
* '12345' with '##-###' -> '12-345'
|
|
498
|
-
* 'AB345' with '##-###' -> 'AB-345'
|
|
499
|
-
* '123456' with '##.####' -> '12.3456'
|
|
500
|
-
* '12345' with '### ##' -> '123 45'
|
|
501
|
-
* 'ABCDEF' with '##-##-##' -> 'AB-CD-EF'
|
|
502
|
-
*/
|
|
503
|
-
static formatMask(rawData, mask) {
|
|
504
|
-
if (!rawData || !mask) {
|
|
505
|
-
return rawData;
|
|
506
|
-
}
|
|
507
|
-
let result = '';
|
|
508
|
-
let dataIndex = 0;
|
|
509
|
-
for (const element of mask) {
|
|
510
|
-
if (dataIndex >= rawData.length) {
|
|
511
|
-
break;
|
|
512
|
-
}
|
|
513
|
-
if (element === '#') {
|
|
514
|
-
result += rawData[dataIndex];
|
|
515
|
-
dataIndex++;
|
|
516
|
-
}
|
|
517
|
-
else {
|
|
518
|
-
result += element;
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
result += rawData.slice(dataIndex);
|
|
522
|
-
return result;
|
|
523
|
-
}
|
|
524
|
-
/**
|
|
525
|
-
* Memoize a function.
|
|
526
|
-
* The function is memoized by the arguments (`JSON.stringify(args)`) and the result is cached for the specified time to live in milliseconds.
|
|
527
|
-
* If the function is called with the same arguments again, the cached result is returned.
|
|
528
|
-
* If the function is called with the same arguments again after the time to live has expired, the function is called again and the result is cached again.
|
|
529
|
-
*
|
|
530
|
-
* @param fn - The function to memoize.
|
|
531
|
-
* @param ttl - The time to live for the cached value in milliseconds.
|
|
532
|
-
* @returns The memoized function.
|
|
533
|
-
*/
|
|
534
|
-
static memoize(fn, ttl = Infinity) {
|
|
535
|
-
const promiseCache = new Map();
|
|
536
|
-
const cache = new Map();
|
|
537
|
-
(0, map_age_cleaner_1.default)(cache);
|
|
538
|
-
const memoized = (async (...args) => {
|
|
539
|
-
const key = JSON.stringify(args);
|
|
540
|
-
const cachedPromise = promiseCache.get(key);
|
|
541
|
-
if (cachedPromise) {
|
|
542
|
-
return cachedPromise;
|
|
543
|
-
}
|
|
544
|
-
const cachedValue = cache.get(key);
|
|
545
|
-
if (cachedValue) {
|
|
546
|
-
return cachedValue.data;
|
|
547
|
-
}
|
|
548
|
-
try {
|
|
549
|
-
const promise = fn(...args);
|
|
550
|
-
promiseCache.set(key, promise);
|
|
551
|
-
const result = await promise;
|
|
552
|
-
cache.set(key, { data: result, maxAge: Date.now() + ttl });
|
|
553
|
-
return result;
|
|
554
|
-
}
|
|
555
|
-
finally {
|
|
556
|
-
promiseCache.delete(key);
|
|
557
|
-
}
|
|
558
|
-
});
|
|
559
|
-
return memoized;
|
|
560
|
-
}
|
|
561
|
-
/**
|
|
562
|
-
* Recursively encodes all string values in an object or string using encodeURIComponent.
|
|
563
|
-
* For objects, it creates a deep clone and processes all string values.
|
|
564
|
-
* For strings, it directly applies the encoding.
|
|
565
|
-
*
|
|
566
|
-
* @param data Input data to encode - can be an object or string
|
|
567
|
-
* @returns Deep cloned object with encoded string values, or encoded string if input was string
|
|
568
|
-
* @example
|
|
569
|
-
* ```typescript
|
|
570
|
-
* const data = { name: 'John Doe', email: 'john@example.com' }
|
|
571
|
-
* const encoded = ApplicationUtils.encodeValuesWithIterator(data)
|
|
572
|
-
* // Result: { name: 'John%20Doe', email: 'john%40example.com' }
|
|
573
|
-
* ```
|
|
574
|
-
*/
|
|
575
|
-
static encodeValuesWithIterator(data) {
|
|
576
|
-
return this.processValues(data, encodeURIComponent);
|
|
577
|
-
}
|
|
578
|
-
/**
|
|
579
|
-
* Recursively decodes all string values in an object or string using decodeURIComponent.
|
|
580
|
-
* For objects, it creates a deep clone and processes all string values.
|
|
581
|
-
* For strings, it directly applies the decoding.
|
|
582
|
-
*
|
|
583
|
-
* @param data Input data to decode - can be an object or string
|
|
584
|
-
* @returns Deep cloned object with decoded string values, or decoded string if input was string
|
|
585
|
-
* @example
|
|
586
|
-
* ```typescript
|
|
587
|
-
* const data = { name: 'John%20Doe', email: 'john%40example.com' }
|
|
588
|
-
* const decoded = ApplicationUtils.decodeValuesWithIterator(data)
|
|
589
|
-
* // Result: { name: 'John Doe', email: 'john@example.com' }
|
|
590
|
-
* ```
|
|
591
|
-
*/
|
|
592
|
-
static decodeValuesWithIterator(data) {
|
|
593
|
-
return this.processValues(data, decodeURIComponent);
|
|
594
|
-
}
|
|
595
|
-
static removeUnderscoreFields(value) {
|
|
596
|
-
if (Array.isArray(value)) {
|
|
597
|
-
return value.map((item) => this.removeUnderscoreFields(item));
|
|
598
|
-
}
|
|
599
|
-
if (typeof value === 'object' && value !== null) {
|
|
600
|
-
const objectField = {};
|
|
601
|
-
for (const key in value) {
|
|
602
|
-
if (!key.startsWith('_')) {
|
|
603
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
604
|
-
objectField[key] = this.removeUnderscoreFields(value[key]);
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
return objectField;
|
|
608
|
-
}
|
|
609
|
-
return value;
|
|
610
|
-
}
|
|
611
|
-
static mod97(string) {
|
|
612
|
-
let checksum = string.slice(0, 2);
|
|
613
|
-
let fragment;
|
|
614
|
-
for (let offset = 2; offset < string.length; offset += 7) {
|
|
615
|
-
fragment = String(checksum) + string.slice(offset, offset + 7);
|
|
616
|
-
checksum = Number.parseInt(fragment, 10) % 97;
|
|
617
|
-
}
|
|
618
|
-
return checksum;
|
|
619
|
-
}
|
|
620
|
-
static toApiError(err) {
|
|
621
|
-
const { type, code, message, data = {}, keyValue } = err;
|
|
622
|
-
const processCode = data.processCode;
|
|
623
|
-
if (code === 11000 && keyValue) {
|
|
624
|
-
data.keyValue = keyValue;
|
|
625
|
-
}
|
|
626
|
-
return new errors_1.ApiError(message, code || types_1.HttpStatusCode.INTERNAL_SERVER_ERROR, data, processCode, type);
|
|
627
|
-
}
|
|
628
|
-
static getPackageJson() {
|
|
629
|
-
const packageJsonPath = node_path_1.default.resolve(process.cwd(), 'package.json');
|
|
630
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
631
|
-
return JSON.parse(node_fs_1.default.readFileSync(packageJsonPath, 'utf8')); // nosemgrep: eslint.detect-non-literal-fs-filename
|
|
632
|
-
}
|
|
633
|
-
static processObject(obj, processor) {
|
|
634
|
-
if (typeof obj === 'string') {
|
|
635
|
-
try {
|
|
636
|
-
return processor(obj);
|
|
637
|
-
}
|
|
638
|
-
catch {
|
|
639
|
-
return obj;
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
if (Array.isArray(obj)) {
|
|
643
|
-
return obj.map((item) => {
|
|
644
|
-
return this.processObject(item, processor);
|
|
645
|
-
});
|
|
646
|
-
}
|
|
647
|
-
if (obj && typeof obj === 'object') {
|
|
648
|
-
if (obj instanceof Date ||
|
|
649
|
-
obj instanceof RegExp ||
|
|
650
|
-
obj instanceof Map ||
|
|
651
|
-
obj instanceof Set ||
|
|
652
|
-
obj instanceof Error ||
|
|
653
|
-
obj instanceof URL ||
|
|
654
|
-
obj instanceof URLSearchParams) {
|
|
655
|
-
return obj;
|
|
656
|
-
}
|
|
657
|
-
const result = {};
|
|
658
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
659
|
-
result[key] = this.processObject(value, processor);
|
|
660
|
-
}
|
|
661
|
-
return result;
|
|
662
|
-
}
|
|
663
|
-
return obj;
|
|
664
|
-
}
|
|
665
|
-
static processValues(data, processor) {
|
|
666
|
-
if (typeof data === 'string') {
|
|
667
|
-
try {
|
|
668
|
-
return processor(data);
|
|
669
|
-
}
|
|
670
|
-
catch {
|
|
671
|
-
return data;
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
const clonedData = (0, lodash_1.cloneDeep)(data);
|
|
675
|
-
return this.processObject(clonedData, processor);
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
exports.ApplicationUtils = ApplicationUtils;
|
|
679
|
-
//# sourceMappingURL=applicationUtils.js.map
|
|
576
|
+
//#endregion
|
|
577
|
+
export { ApplicationUtils };
|