const fs = require('fs'); const sleep = (ms) => { return new Promise(resolve => { console.log(`${(ms / 1000).toFixed(2)}s SHOULD WAIT`); setTimeout(resolve, ms); }) } function convertStringToJson(string) { try { return JSON.parse(string); } catch (error) { console.log(error); return {}; } } const checkCondition = ({ data, condition }, conditionArray = new Set()) => { let ands = true; for (const key in condition) { const conditionValue = condition[key]; if (key == 'or') { let ors = false; for (const cond of conditionValue) { // const orConditionChecking = checkCondition({ data, condition: cond }, conditionArray); // ors = ors || orConditionChecking.ok; ors = ors || checkCondition({ data, condition: cond }); // conditionArray = new Set([...conditionArray, ...orConditionChecking.conditionArray]) if (ors) break; } ands = ands && ors; } else { if (data[key] == 'true') data[key] = true; if (data[key] == 'false') data[key] = false; switch (key) { case [Op.gt]: ands = ands && data[key] > conditionValue; // if (!conditionArray.has(key)) conditionArray.add(key); break; default: ands = ands && data[key] == conditionValue; // if (!conditionArray.has(key)) conditionArray.add(key); } } if (!ands) { // return { ok: false, conditionArray }; return false; } } // return { ok: true, conditionArray }; return true; }; function checkingOptions({ question, answer }) { let options = convertStringToJson(question?.options) || [], inputType = +question?.inputType || -1; // console.log("options", options) if (!Array.isArray(options)) options = [options]; const OptionsValidValue = options.map((option) => { return parseValue(option?.value); }) const questionAnswer = answer[question.id]; if (questionAnswer === null || questionAnswer === undefined) return { ok: false } let min = question?.min, max = question?.max, length = question?.length, validation = question?.validation; switch (inputType) { case 1: //TEXT case 3: if (typeof questionAnswer !== "string") { return { statusCode: 400, message: ErrorMessages.InvalidData } } if (length && isNumber(length) && questionAnswer.length > +length) { return { statusCode: 400, message: ErrorMessages.InvalidTextLength(question.id, length) } } if (validation && isValidRegex(validation)) { let validationRegex = new RegExp(validation); if (!validationRegex.test(questionAnswer)) return { statusCode: 400, message: ErrorMessages.InvalidTextPattern } } break; case 2: //NUMBER if (!isNumber(questionAnswer)) { return { statusCode: 400, message: ErrorMessages.InvalidNumber(question.id) } } if (min && isNumber(min) && +min > +questionAnswer) return { statusCode: 400, message: ErrorMessages.InvalidMinNumber(question.id, min) } if (max && isNumber(max) && +max < +questionAnswer) return { statusCode: 400, message: ErrorMessages.InvalidMaxNumber(question.id, max) } break; case 4: // tak entekhab case 6: if (!OptionsValidValue.includes(questionAnswer)) { return { statusCode: 400, message: ErrorMessages.InvalidSelectedOption(question.id) } } break; case 5: // chand entekhab case 7: if (Array.isArray(questionAnswer)) { for (let ans of questionAnswer) { if (!OptionsValidValue.includes(ans)) return { statusCode: 400, message: ErrorMessages.InvalidAdditionalData(question.id) }; } } else if (typeof questionAnswer === "string") { for (let ans of questionAnswer.split(",")) { if (!OptionsValidValue.includes(ans)) return { statusCode: 400, message: ErrorMessages.InvalidAdditionalData(question.id) }; } } else { return { statusCode: 400, message: ErrorMessages.InvalidData(question.id) }; } break; case 8: if (!isValidDateStrict(questionAnswer)) return { statusCode: 400, message: ErrorMessages.InvalidDate(question.id) }; const questionDate = new Date(questionAnswer); if (min) { if (isValidDateStrict(min)) { if (new Date(min) > questionDate) return { statusCode: 400, message: ErrorMessages.InvalidMinDate(question.id) } } else if (min === "now()") { if (new Date() > questionDate) return { statusCode: 400, message: ErrorMessages.InvalidMinDate(question.id) } } else if (min.includes("day")) { let days = min.match(/day(s)?\(([0-9]{1,2})\)/)[2]; if ( !isNaN(+days) // && // Date.now() < questionDate && Math.abs(Date.now() - questionDate.getTime()) > convertDaysToSeconds(days) ) { return { statusCode: 400, message: ErrorMessages.InvalidDayMinDate(question.id, days) } } } } if (max) { if (isValidDateStrict(max)) { if (new Date(max) < questionDate) return { statusCode: 400, message: ErrorMessages.InvalidMaxDate(question.id) } } else if (max === "now()") { if (new Date() < questionDate) return { statusCode: 400, message: ErrorMessages.InvalidMaxDate(question.id) } } else if (max.includes("day")) { let days = max.match(/day(s)?\(([0-9]{1,2})\)/)[2]; if ( !isNaN(+days) // && // Date.now() < questionDate && Math.abs(Date.now() - questionDate.getTime()) < convertDaysToSeconds(days) ) { return { statusCode: 400, message: ErrorMessages.InvalidDayMaxDate(question.id, days) } } } } break; case 10: if (!isBoolean(questionAnswer)) return { statusCode: 400, message: ErrorMessages.InvalidBooleanData(question.id) } break; default: return { statusCode: 400, message: ErrorMessages.InvalidInputType(question.id) } } return true; } async function checkingConditionTypes({ fQ, question, mojazData = [], answer }) { let check = false, checkOption = false, inputType = 10; let condition = {}, isRequired = null; let requiredCondition = {}, validation = null; requiredCondition = question?.requiredCondition || {}; validation = question?.validation || null; condition = convertStringToJson(fQ?.condition) || convertStringToJson(question?.condition) || null; isRequired = returnBooleanOfArguments([fQ?.isRequired, question?.required]); inputType = question?.inputType || 10; console.log("question id:", question.id) console.log("requiredCondition", requiredCondition); // console.log("validation",validation); console.log("condition", condition); console.log("isRequired", isRequired); // console.log("options",options); console.log("inputType", inputType); //1.check requiredConditions //2.check Conditions and check they required or not //3.check Options and check they required or not //4.check validation //5.remove extra data //1.check requiredConditions check = checkCondition({ data: answer, condition: requiredCondition }) if (check.ok === false) throw `${defects[question.id]}`; mojazData = [...new Set([...mojazData, ...check.conditionArray])]; console.log("DONE SOLVE requiredConditions") //2.check Conditions and check they required or not check = checkCondition({ data: answer, condition: condition }) checkOption = checkingOptions({ question, answer }); if ((check.ok === false || checkOption.ok === false) && isRequired === true) throw `${defects[question.id]}`; if (checkOption.ok === true) mojazData.push(`${question.id}`); mojazData = [...new Set([...mojazData, ...check.conditionArray])]; console.log("DONE SOLVE condition") //3.check Validation (ro bayad beporsam az aghaye sadeghi) console.log("mojazDataAfter-requiredCondition-condition", mojazData); return { mojazData } } function isBoolean(value) { if(typeof value==="string"){ if(value?.trim()?.toLowerCase()=="true") value=true; else if(value?.trim()?.toLowerCase()=="false") value=false; } return typeof value === 'boolean'; } function safeRegexFromString(regexString) { try { const parts = regexString.split('/'); if (parts.length < 3) { // اگر / وجود نداشت، کل رشته به عنوان pattern در نظر گرفته می‌شود return new RegExp(regexString); } const pattern = parts.slice(1, -1).join('/'); // برای الگوهایی که شامل / هستند const flags = parts[parts.length - 1]; return new RegExp(pattern, flags); } catch (error) { console.error("Invalid regex string:", error); // return null; throw `invalidRegexFrom:${regexString}`; } } function returnBooleanOfArguments(array){ if(!Array.isArray(array)) return false; for(let i of array){ if(isBoolean(i)) return !!i; } return false; } function isNumber(value) { // return typeof value === 'number' && !isNaN(value) && isFinite(value); // return !isNaN(parseInt(value)) && isFinite(value); return !isNaN(+value) && isFinite(+value); } function parseValue(value) { // اگر ورودی از قبل رشته نباشد، همان مقدار را برگردانیم. if (typeof value !== 'string') { return value; } const trimmed = value.trim(); // // بررسی مقادیر بولی // if (trimmed.toLowerCase() === 'true') return true; // if (trimmed.toLowerCase() === 'false') return false; // بررسی null و undefined if (trimmed.toLowerCase() === 'null') return null; if (trimmed.toLowerCase() === 'undefined') return undefined; // بررسی عدد // اگر رشته به صورت عددی تفسیر شود (توجه کنید که isNaN('') === false، پس باید رشته خالی هم چک شود) if (trimmed !== '' && isNumber(trimmed)) { return Number(trimmed); } // بررسی تاریخ // Date.parse در صورت عدم توانایی در تبدیل، مقدار NaN برمی‌گرداند. const timestamp = Date.parse(trimmed); if (!isNaN(timestamp)) { return new Date(trimmed); } // بررسی آرایه یا شیء (در صورتی که رشته با { یا [ شروع و به ترتیب با } یا ] پایان یابد) if ( (trimmed.startsWith('{') && trimmed.endsWith('}')) || (trimmed.startsWith('[') && trimmed.endsWith(']')) ) { try { return JSON.parse(trimmed); } catch (e) { // در صورت بروز خطا، به ادامه کد می‌رویم } } // در نهایت اگر هیچ یک از شرایط بالا صدق نکرد، // رشته اصلی را برمی‌گردانیم. return value; } function isValidDateStrict(date) { if (date instanceof Date && !isNaN(date.getTime())) { return true; } if (typeof date === "string") { const parsed = new Date(date); return parsed instanceof Date && !isNaN(parsed.getTime()); } return false; } function convertDaysToSeconds(days){ return days * 24 * 3600 * 1000; } function isValidRegex(str) { try { if (str.startsWith('/')) { let endSlashIndex = -1; let backslashCount = 0; // شروع جستجو از ایندکس ۱ (بعد از اسلش اول) for (let i = 1; i < str.length; i++) { if (str[i] === '\\') { backslashCount++; } else { if (str[i] === '/' && backslashCount % 2 === 0) { endSlashIndex = i; break; } backslashCount = 0; } } // اگر اسلش پایانی پیدا نشد if (endSlashIndex === -1) { return false; } const pattern = str.slice(1, endSlashIndex); const modifiers = str.slice(endSlashIndex + 1); new RegExp(pattern, modifiers); } else { new RegExp(str); } return true; } catch (e) { return false; } } async function saveLargeObject(obj, filename) { const ws = fs.createWriteStream(filename, { encoding: 'utf8' }); ws.write('{\n'); const entries = Object.entries(obj); for (let i = 0; i < entries.length; i++) { const [key, value] = entries[i]; // stringify مقدار const chunk = JSON.stringify(value, null, 2) .split('\n') .map((line, idx) => idx === 0 ? ` "${key}": ${line}` : ` ${line}`) .join('\n'); // اگر اولین نیست، قبلش کاما بگذار ws.write((i > 0 ? ',\n' : '') + chunk); } ws.write('\n}\n'); ws.end(); await new Promise((res, rej) => { ws.on('finish', res); ws.on('error', rej); }); console.log(`فایل ${filename} ذخیره شد.`); } module.exports = { sleep, convertStringToJson, isValidRegex, convertDaysToSeconds, isValidDateStrict, parseValue, isNumber, returnBooleanOfArguments, safeRegexFromString, isBoolean, checkingConditionTypes, checkingOptions, checkCondition, saveLargeObject }