forked from Yara724/api
Merge pull request 'refresh blame inquirys script added (look at the comments section for env)' (#103) from s.hajizadeh/yara724api:main into main
Reviewed-on: Yara724/api#103
This commit is contained in:
606
scripts/refresh-blame-inquiries.js
Normal file
606
scripts/refresh-blame-inquiries.js
Normal file
@@ -0,0 +1,606 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/*
|
||||
* One-time script to replace mocked blameCases party vehicle inquiry data with
|
||||
* Tejarat block inquiry responses.
|
||||
*
|
||||
* Defaults to DRY_RUN=true. Set DRY_RUN=false to write changes.
|
||||
*/
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const mongoose = require("mongoose");
|
||||
|
||||
const loadedEnvFiles = loadEnvFiles(process.env.ENV_FILE || ".env");
|
||||
|
||||
const LETTER_TO_NUMBER = {
|
||||
"الف": 1,
|
||||
"ب": 2,
|
||||
"ت": 3,
|
||||
"ج": 4,
|
||||
"د": 5,
|
||||
"س": 6,
|
||||
"ص": 7,
|
||||
"ط": 8,
|
||||
"ع": 9,
|
||||
"ق": 10,
|
||||
"ل": 11,
|
||||
"م": 12,
|
||||
"ن": 13,
|
||||
"و": 14,
|
||||
"ه": 15,
|
||||
"ی": 16,
|
||||
"ر": 17,
|
||||
"ک": 18,
|
||||
"ژ": 19,
|
||||
"پ": 20,
|
||||
"ظ": 24,
|
||||
"ض": 25,
|
||||
"ز": 41,
|
||||
"ش": 42,
|
||||
"گ": 43,
|
||||
"ث": 44,
|
||||
D: 45,
|
||||
S: 46,
|
||||
"ح": 47,
|
||||
"ف": 48,
|
||||
};
|
||||
|
||||
const NUMBER_TO_LETTER = Object.fromEntries(
|
||||
Object.entries(LETTER_TO_NUMBER).map(([letter, number]) => [String(number), letter]),
|
||||
);
|
||||
|
||||
const config = {
|
||||
mongoUri: requiredEnv("MONGO_URL", "MONGODB_URI", "DATABASE_URL"),
|
||||
collectionName: process.env.BLAME_COLLECTION || "blameCases",
|
||||
mongoDbName: process.env.MONGO_DB_NAME || "",
|
||||
thirdPartyUrl:
|
||||
process.env.TEJARAT_THIRD_PARTY_URL ||
|
||||
"http://82.99.202.245:3027/block-inquiry-tejarat",
|
||||
carBodyUrl:
|
||||
process.env.TEJARAT_CAR_BODY_URL ||
|
||||
"http://82.99.202.245:3027/block-inquiry-tejarat/badane",
|
||||
thirdPartyToken:
|
||||
process.env.TEJARAT_THIRD_PARTY_TOKEN || process.env.TEJARAT_TOKEN || "",
|
||||
carBodyToken:
|
||||
process.env.TEJARAT_CAR_BODY_TOKEN || process.env.TEJARAT_TOKEN || "",
|
||||
rateLimitPerMinute: Number(process.env.RATE_LIMIT_PER_MINUTE || 5),
|
||||
retryEnabled: String(process.env.RETRY_ENABLED ?? "true").toLowerCase() !== "false",
|
||||
retryCount: Number(process.env.RETRY_COUNT || 3),
|
||||
retryDelayMs: Number(process.env.RETRY_DELAY_MS || 2000),
|
||||
dryRun: String(process.env.DRY_RUN ?? "true").toLowerCase() !== "false",
|
||||
limit: process.env.LIMIT ? Number(process.env.LIMIT) : 0,
|
||||
publicId: process.env.PUBLIC_ID || "",
|
||||
};
|
||||
|
||||
let lastRequestAt = 0;
|
||||
|
||||
main().catch(async (error) => {
|
||||
console.error("[fatal]", error && error.stack ? error.stack : error);
|
||||
await mongoose.disconnect().catch(() => undefined);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
|
||||
async function main() {
|
||||
validateConfig();
|
||||
|
||||
console.log("script runned successfully");
|
||||
console.log(
|
||||
"[config] envFiles=" +
|
||||
(loadedEnvFiles.length ? loadedEnvFiles.join(",") : "none") +
|
||||
", dbName=" +
|
||||
(config.mongoDbName || "from-url-or-driver-default") +
|
||||
", collection=" +
|
||||
config.collectionName +
|
||||
", dryRun=" +
|
||||
config.dryRun +
|
||||
", rateLimitPerMinute=" +
|
||||
config.rateLimitPerMinute +
|
||||
", retryEnabled=" +
|
||||
config.retryEnabled +
|
||||
", retryCount=" +
|
||||
config.retryCount,
|
||||
);
|
||||
|
||||
await mongoose.connect(config.mongoUri, {
|
||||
autoIndex: false,
|
||||
dbName: config.mongoDbName || undefined,
|
||||
});
|
||||
const collection = mongoose.connection.collection(config.collectionName);
|
||||
|
||||
const query = {
|
||||
type: { $in: ["THIRD_PARTY", "CAR_BODY"] },
|
||||
};
|
||||
if (config.publicId) query.publicId = config.publicId;
|
||||
|
||||
const totalDocs = await collection.countDocuments(query);
|
||||
console.log(`total docs that we have to edit: ${totalDocs}`);
|
||||
|
||||
const cursor = collection
|
||||
.find(query, {
|
||||
projection: {
|
||||
publicId: 1,
|
||||
requestNo: 1,
|
||||
type: 1,
|
||||
parties: 1,
|
||||
},
|
||||
})
|
||||
.sort({ createdAt: 1, _id: 1 });
|
||||
|
||||
if (config.limit > 0) cursor.limit(config.limit);
|
||||
|
||||
const summary = {
|
||||
docsSeen: 0,
|
||||
docsChanged: 0,
|
||||
partiesInquired: 0,
|
||||
partiesSkipped: 0,
|
||||
partiesFailed: 0,
|
||||
};
|
||||
|
||||
for await (const doc of cursor) {
|
||||
summary.docsSeen += 1;
|
||||
const label = doc.publicId || doc.requestNo || String(doc._id);
|
||||
const requestId = String(doc._id);
|
||||
console.log(`currently inquiry for doc with ${label} and requestId ${requestId}`);
|
||||
|
||||
const parties = Array.isArray(doc.parties) ? clone(doc.parties) : [];
|
||||
let docChanged = false;
|
||||
|
||||
for (let index = 0; index < parties.length; index += 1) {
|
||||
const party = parties[index];
|
||||
const partyLabel = `${label} parties[${index}] role=${party && party.role ? party.role : "-"}`;
|
||||
|
||||
const input = buildInquiryInput(doc, party);
|
||||
if (!input.ok) {
|
||||
summary.partiesSkipped += 1;
|
||||
console.warn(`[skip] ${partyLabel}: ${input.reason}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(`[request] ${partyLabel} body=${JSON.stringify(input.body)}`);
|
||||
|
||||
try {
|
||||
const inquiryResponse = await inquiryWithRetry(doc.type, input.body);
|
||||
const successful = isInquirySuccessful(doc.type, inquiryResponse);
|
||||
console.log(
|
||||
`[response] ${partyLabel} successful=${successful} body=${JSON.stringify(inquiryResponse)}`,
|
||||
);
|
||||
|
||||
if (!successful) {
|
||||
summary.partiesFailed += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
parties[index] = applyInquiryToParty(doc.type, party, inquiryResponse, input.plate);
|
||||
summary.partiesInquired += 1;
|
||||
docChanged = true;
|
||||
} catch (error) {
|
||||
summary.partiesFailed += 1;
|
||||
console.error(`[error] ${partyLabel}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!docChanged) {
|
||||
console.log(`[doc] ${label} no changes`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (config.dryRun) {
|
||||
console.log(`[dry-run] ${label} would update parties array`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const result = await collection.updateOne(
|
||||
{ _id: doc._id },
|
||||
{
|
||||
$set: {
|
||||
parties,
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
},
|
||||
);
|
||||
summary.docsChanged += result.modifiedCount;
|
||||
console.log(`[update] ${label} matched=${result.matchedCount} modified=${result.modifiedCount}`);
|
||||
}
|
||||
|
||||
await mongoose.disconnect();
|
||||
console.log(`[done] ${JSON.stringify(summary)}`);
|
||||
}
|
||||
|
||||
function buildInquiryInput(doc, party) {
|
||||
if (!party || typeof party !== "object") return { ok: false, reason: "party is empty" };
|
||||
if (!party.vehicle || typeof party.vehicle !== "object") {
|
||||
return { ok: false, reason: "party.vehicle is missing" };
|
||||
}
|
||||
|
||||
const plate = extractPlate(party);
|
||||
const nationalCode =
|
||||
cleanString(party.person && party.person.nationalCodeOfInsurer) ||
|
||||
cleanString(party.person && party.person.nationalCodeOfDriver);
|
||||
|
||||
if (!plate) return { ok: false, reason: "Plk1/Plk2/Plk3/PlkSrl not found" };
|
||||
if (!nationalCode) return { ok: false, reason: "nationalCodeOfInsurer/nationalCodeOfDriver missing" };
|
||||
|
||||
const serialLetter = NUMBER_TO_LETTER[String(plate.Plk2)] || cleanString(plate.Plk2);
|
||||
if (!serialLetter) return { ok: false, reason: `no Persian letter mapping for Plk2=${plate.Plk2}` };
|
||||
|
||||
if (doc.type === "CAR_BODY") {
|
||||
return {
|
||||
ok: true,
|
||||
plate,
|
||||
body: {
|
||||
part1: toNumber(plate.Plk1),
|
||||
part2: serialLetter,
|
||||
part3: toNumber(plate.Plk3),
|
||||
part4: toNumber(plate.PlkSrl),
|
||||
nationalCode,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (doc.type === "THIRD_PARTY") {
|
||||
return {
|
||||
ok: true,
|
||||
plate,
|
||||
body: {
|
||||
leftTwoDigits: String(plate.Plk1),
|
||||
serialLetter,
|
||||
threeDigits: String(plate.Plk3),
|
||||
rightTwoDigits: String(plate.PlkSrl),
|
||||
nationalCode,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return { ok: false, reason: `unsupported type=${doc.type}` };
|
||||
}
|
||||
|
||||
function parsePlateId(plateId) {
|
||||
const value = cleanString(plateId);
|
||||
if (!value) return null;
|
||||
|
||||
const parts = value.split("-").map((part) => normalizePlateNumber(part));
|
||||
if (parts.length !== 4) return null;
|
||||
|
||||
const thirdPartIsLetter = LETTER_TO_NUMBER[parts[2]] !== undefined;
|
||||
const fourthPartIsLetter = LETTER_TO_NUMBER[parts[3]] !== undefined;
|
||||
|
||||
if (thirdPartIsLetter) {
|
||||
return {
|
||||
Plk1: parts[1],
|
||||
Plk2: String(LETTER_TO_NUMBER[parts[2]]),
|
||||
Plk3: parts[3],
|
||||
PlkSrl: parts[0],
|
||||
};
|
||||
}
|
||||
|
||||
if (fourthPartIsLetter) {
|
||||
return {
|
||||
Plk1: parts[2],
|
||||
Plk2: String(LETTER_TO_NUMBER[parts[3]]),
|
||||
Plk3: parts[1],
|
||||
PlkSrl: parts[0],
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function extractPlate(party) {
|
||||
const plateFromPlateId = parsePlateId(party.vehicle && party.vehicle.plateId);
|
||||
if (plateFromPlateId) return plateFromPlateId;
|
||||
|
||||
const candidates = [
|
||||
party.vehicle && party.vehicle.inquiry && party.vehicle.inquiry.mapped,
|
||||
party.vehicle && party.vehicle.inquiry && party.vehicle.inquiry.raw,
|
||||
party.vehicle && party.vehicle.inquiry,
|
||||
party.vehicle,
|
||||
].filter(Boolean);
|
||||
|
||||
for (const candidate of candidates) {
|
||||
const Plk1 = firstPresent(candidate.Plk1, candidate.platePartOne);
|
||||
const Plk2 = firstPresent(candidate.Plk2, candidate.plateLetterid, candidate.plateLetterId);
|
||||
const Plk3 = firstPresent(candidate.Plk3, candidate.platePartThree);
|
||||
const PlkSrl = firstPresent(candidate.PlkSrl, candidate.plkSrl, candidate.plateSerialNumber);
|
||||
|
||||
if (
|
||||
Plk1 !== undefined &&
|
||||
Plk2 !== undefined &&
|
||||
Plk3 !== undefined &&
|
||||
PlkSrl !== undefined
|
||||
) {
|
||||
return {
|
||||
Plk1: normalizePlateNumber(Plk1),
|
||||
Plk2: normalizePlateNumber(Plk2),
|
||||
Plk3: normalizePlateNumber(Plk3),
|
||||
PlkSrl: normalizePlateNumber(PlkSrl),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async function inquiryWithRetry(type, body) {
|
||||
const url = type === "CAR_BODY" ? config.carBodyUrl : config.thirdPartyUrl;
|
||||
const token = type === "CAR_BODY" ? config.carBodyToken : config.thirdPartyToken;
|
||||
let lastError;
|
||||
|
||||
const maxAttempts = config.retryEnabled ? config.retryCount : 1;
|
||||
|
||||
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
||||
await waitForRateLimit();
|
||||
try {
|
||||
console.log("[http] " + type + " attempt=" + attempt + "/" + maxAttempts);
|
||||
return await postJson(url, token, body);
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
console.error(`[retry] ${type} attempt=${attempt} failed: ${error.message}`);
|
||||
if (attempt < maxAttempts) {
|
||||
await sleep(config.retryDelayMs * attempt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError;
|
||||
}
|
||||
|
||||
async function postJson(url, token, body) {
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
authorization: `Bearer ${token}`,
|
||||
"content-type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
const text = await response.text();
|
||||
let data;
|
||||
try {
|
||||
data = text ? JSON.parse(text) : null;
|
||||
} catch {
|
||||
data = text;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${JSON.stringify(data)}`);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
function isInquirySuccessful(type, response) {
|
||||
if (type === "CAR_BODY") return response && response.isSuccess === true && response.data;
|
||||
return response && response.resultStatus === true;
|
||||
}
|
||||
|
||||
function applyInquiryToParty(type, party, response, originalPlate) {
|
||||
const next = clone(party);
|
||||
if (!next.vehicle) next.vehicle = {};
|
||||
if (!next.insurance) next.insurance = {};
|
||||
|
||||
const mapped = type === "CAR_BODY"
|
||||
? normalizeCarBodyResponse(response, originalPlate)
|
||||
: normalizeThirdPartyResponse(response, originalPlate);
|
||||
const plateId = buildPlateId(originalPlate);
|
||||
|
||||
next.vehicle.plateId = plateId || next.vehicle.plateId;
|
||||
|
||||
next.vehicle.inquiry = {
|
||||
source: type === "CAR_BODY" ? "TEJARAT_CAR_BODY_BLOCK_INQUIRY" : "TEJARAT_BLOCK_INQUIRY",
|
||||
raw: type === "CAR_BODY" ? normalizeCarBodyRaw(response, originalPlate) : response,
|
||||
mapped,
|
||||
refreshedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
if (type === "CAR_BODY") {
|
||||
next.vehicle.inquiry.responseMeta = {
|
||||
isSuccess: response.isSuccess,
|
||||
statusCode: response.statusCode,
|
||||
message: response.message,
|
||||
};
|
||||
}
|
||||
|
||||
if (type === "CAR_BODY") {
|
||||
next.vehicle.name = mapped.vehicleSystemTitle || next.vehicle.name;
|
||||
next.vehicle.type = mapped.vehicleGroupTitle || next.vehicle.type;
|
||||
next.insurance.policyNumber = mapped.printNumber || next.insurance.policyNumber;
|
||||
next.insurance.company = mapped.companyName || next.insurance.company;
|
||||
next.insurance.startDate = mapped.beginDate || next.insurance.startDate;
|
||||
next.insurance.endDate = mapped.endDate || next.insurance.endDate;
|
||||
next.insurance.carBodyInsurance = {
|
||||
policyNumber: mapped.printNumber,
|
||||
startDate: mapped.beginDate,
|
||||
endDate: mapped.endDate,
|
||||
insurerCompany: mapped.companyName,
|
||||
coverages: Array.isArray(mapped.coverages) ? mapped.coverages : [],
|
||||
};
|
||||
return next;
|
||||
}
|
||||
|
||||
next.vehicle.name = mapped.vehiclePersianName || mapped.MapTypNam || "اطلاعات این گزینه در استعلام موجود نیست";
|
||||
next.vehicle.type = mapped.persianCarType || mapped.MapUsageName || next.vehicle.type;
|
||||
next.insurance.policyNumber = mapped.insuranceNumber || next.insurance.policyNumber;
|
||||
next.insurance.company = mapped.companyPersianName || next.insurance.company;
|
||||
next.insurance.financialCeiling = mapped.financeCoverage || next.insurance.financialCeiling;
|
||||
next.insurance.startDate = mapped.persianStartDate || next.insurance.startDate;
|
||||
next.insurance.endDate = mapped.persianEndDate || next.insurance.endDate;
|
||||
return next;
|
||||
}
|
||||
|
||||
function buildPlateId(plate) {
|
||||
if (!plate) return "";
|
||||
const serialLetter = NUMBER_TO_LETTER[String(plate.Plk2)] || cleanString(plate.Plk2);
|
||||
if (!serialLetter) return "";
|
||||
return (
|
||||
cleanString(plate.PlkSrl) +
|
||||
"-" +
|
||||
cleanString(plate.Plk1) +
|
||||
"-" +
|
||||
serialLetter +
|
||||
"-" +
|
||||
cleanString(plate.Plk3)
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeThirdPartyResponse(response, originalPlate) {
|
||||
return {
|
||||
...response,
|
||||
Plk1: toNumber(originalPlate.Plk1),
|
||||
Plk2: toNumber(originalPlate.Plk2),
|
||||
Plk3: toNumber(originalPlate.Plk3),
|
||||
PlkSrl: toNumber(originalPlate.PlkSrl),
|
||||
CompanyName: response.companyPersianName,
|
||||
CompanyCode: response.companyId,
|
||||
LastCompanyDocumentNumber: response.insuranceNumber,
|
||||
FinancialCvrCptl: response.financeCoverage,
|
||||
IssueDate: response.hIsuDte || response.persianStartDate,
|
||||
SatrtDate: response.persianStartDate,
|
||||
EndDate: response.persianEndDate,
|
||||
MapTypNam: response.vehiclePersianName,
|
||||
MapUsageName: response.MapUsageName,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeCarBodyRaw(response, originalPlate) {
|
||||
const data = response.data || {};
|
||||
return {
|
||||
...data,
|
||||
Plk1: toNumber(originalPlate.Plk1),
|
||||
Plk2: toNumber(originalPlate.Plk2),
|
||||
Plk3: toNumber(originalPlate.Plk3),
|
||||
PlkSrl: toNumber(originalPlate.PlkSrl),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeCarBodyResponse(response, originalPlate) {
|
||||
const data = response.data || {};
|
||||
return {
|
||||
...data,
|
||||
Plk1: toNumber(originalPlate.Plk1),
|
||||
Plk2: toNumber(originalPlate.Plk2),
|
||||
Plk3: toNumber(originalPlate.Plk3),
|
||||
PlkSrl: toNumber(originalPlate.PlkSrl),
|
||||
CompanyName: data.companyName,
|
||||
CompanyCode: data.companyId,
|
||||
LastCompanyDocumentNumber: data.printNumber,
|
||||
IssueDate: data.issueDate,
|
||||
SatrtDate: data.beginDate,
|
||||
EndDate: data.endDate,
|
||||
MtrNum: data.motorNumber,
|
||||
ShsNum: data.chassisNumber,
|
||||
VinNumberField: data.vin,
|
||||
MapTypNam: data.vehicleSystemTitle,
|
||||
};
|
||||
}
|
||||
|
||||
async function waitForRateLimit() {
|
||||
const minDelayMs = Math.ceil(60000 / config.rateLimitPerMinute);
|
||||
const elapsed = Date.now() - lastRequestAt;
|
||||
if (lastRequestAt > 0 && elapsed < minDelayMs) {
|
||||
const waitMs = minDelayMs - elapsed;
|
||||
console.log(`[rate-limit] waiting ${waitMs}ms`);
|
||||
await sleep(waitMs);
|
||||
}
|
||||
lastRequestAt = Date.now();
|
||||
}
|
||||
|
||||
function validateConfig() {
|
||||
if (!config.mongoUri) throw new Error("MONGO_URL is required");
|
||||
if (!Number.isFinite(config.rateLimitPerMinute) || config.rateLimitPerMinute <= 0) {
|
||||
throw new Error("RATE_LIMIT_PER_MINUTE must be a positive number");
|
||||
}
|
||||
if (!Number.isFinite(config.retryCount) || config.retryCount <= 0) {
|
||||
throw new Error("RETRY_COUNT must be a positive number");
|
||||
}
|
||||
if (!config.thirdPartyToken) {
|
||||
throw new Error("TEJARAT_THIRD_PARTY_TOKEN or TEJARAT_TOKEN is required");
|
||||
}
|
||||
if (!config.carBodyToken) {
|
||||
throw new Error("TEJARAT_CAR_BODY_TOKEN or TEJARAT_TOKEN is required");
|
||||
}
|
||||
}
|
||||
|
||||
function loadEnvFiles(filePath) {
|
||||
const candidates = path.isAbsolute(filePath)
|
||||
? [filePath]
|
||||
: [
|
||||
path.resolve(process.cwd(), filePath),
|
||||
path.resolve(__dirname, "..", filePath),
|
||||
];
|
||||
|
||||
const loaded = [];
|
||||
for (const candidate of [...new Set(candidates)]) {
|
||||
if (!fs.existsSync(candidate)) continue;
|
||||
loadEnvFile(candidate);
|
||||
loaded.push(candidate);
|
||||
}
|
||||
return loaded;
|
||||
}
|
||||
|
||||
function loadEnvFile(filePath) {
|
||||
const resolved = path.resolve(process.cwd(), filePath);
|
||||
if (!fs.existsSync(resolved)) return;
|
||||
|
||||
const lines = fs.readFileSync(resolved, "utf8").split(/\r?\n/);
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith("#")) continue;
|
||||
const equalIndex = trimmed.indexOf("=");
|
||||
if (equalIndex === -1) continue;
|
||||
|
||||
const key = trimmed.slice(0, equalIndex).trim();
|
||||
let value = trimmed.slice(equalIndex + 1).trim();
|
||||
if (
|
||||
(value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))
|
||||
) {
|
||||
value = value.slice(1, -1);
|
||||
}
|
||||
if (key && process.env[key] === undefined) process.env[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
function requiredEnv(...names) {
|
||||
for (const name of names) {
|
||||
if (process.env[name]) return process.env[name];
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function firstPresent(...values) {
|
||||
return values.find((value) => value !== undefined && value !== null && value !== "");
|
||||
}
|
||||
|
||||
function normalizePlateNumber(value) {
|
||||
const cleaned = normalizeDigits(cleanString(value));
|
||||
return cleaned === "" ? value : cleaned;
|
||||
}
|
||||
|
||||
function normalizeDigits(value) {
|
||||
return cleanString(value).replace(/./g, (char) => {
|
||||
const code = char.charCodeAt(0);
|
||||
if (code >= 0x06f0 && code <= 0x06f9) return String(code - 0x06f0);
|
||||
if (code >= 0x0660 && code <= 0x0669) return String(code - 0x0660);
|
||||
return char;
|
||||
});
|
||||
}
|
||||
|
||||
function toNumber(value) {
|
||||
const number = Number(value);
|
||||
return Number.isFinite(number) ? number : value;
|
||||
}
|
||||
|
||||
function cleanString(value) {
|
||||
return value === undefined || value === null ? "" : String(value).trim();
|
||||
}
|
||||
|
||||
function clone(value) {
|
||||
return JSON.parse(JSON.stringify(value));
|
||||
}
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
Reference in New Issue
Block a user