|
|
const fs = require('fs'); |
|
|
const { v4: uuidv4 } = require('uuid'); |
|
|
const _ = require("lodash"); |
|
|
const crypto = require("crypto"); |
|
|
const bcrypt = require("bcrypt"); |
|
|
|
|
|
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} ذخیره شد.`); |
|
|
} |
|
|
|
|
|
|
|
|
async function realPayloadGenerator(preferedPayload={},user,{restricted=false,workingTimeLimit=true,canRefreshSession=true,passwordLogin=true,otpLogin=true}={}) { |
|
|
user = user || await createUser(); |
|
|
const roleId = preferedPayload?.roleId || 23; |
|
|
const networkId = preferedPayload?.networkId || 10001; |
|
|
let network = await db.network.findByPk(networkId,{ |
|
|
raw:true, |
|
|
paranoid:false |
|
|
}); |
|
|
if (!network) { |
|
|
network = await db.network.create({ |
|
|
id: networkId, |
|
|
name: `GodNetwork${networkId}`, |
|
|
level: preferedPayload?.level || 1, |
|
|
[`level${preferedPayload?.level || 1}Id`]:networkId, |
|
|
siamId: uuidv4() |
|
|
}); |
|
|
} |
|
|
|
|
|
let role = await db.role.findByPk(roleId,{ |
|
|
raw:true, |
|
|
paranoid:false |
|
|
}); |
|
|
|
|
|
|
|
|
if (!role) { |
|
|
role = await db.role.create({ |
|
|
id: roleId, |
|
|
name: `GodRole ${roleId}`, |
|
|
description: `Test Role ${roleId} Description`, |
|
|
workingTimeLimit, |
|
|
canRefreshSession, |
|
|
passwordLogin, |
|
|
otpLogin, |
|
|
restricted |
|
|
}); |
|
|
|
|
|
} |
|
|
|
|
|
let position = await db.position.create({ |
|
|
name: "Test Position 1", |
|
|
description: "Test Position 1 With 1 Max User", |
|
|
maxUsers: preferedPayload?.maxUsers || 1, |
|
|
}); |
|
|
|
|
|
let userRole = await db.userRole.create({ |
|
|
id: -getRandomNumber(1e7, 1e8-1), |
|
|
userId: user.id, |
|
|
roleId: role.id, |
|
|
networkId: network.id, |
|
|
positionId: position.id, |
|
|
description: `Test UserRole ${user.id} Description`, |
|
|
expirationTime:preferedPayload?.expirationTime || null, |
|
|
}); |
|
|
|
|
|
let networkPosition = await db.networkPosition.create({ |
|
|
networkId: +await network.id, |
|
|
positionId: +await position.id, |
|
|
level: +await network.level |
|
|
}); |
|
|
|
|
|
let positionRole = await db.positionRole.create({ |
|
|
roleId: role.id, |
|
|
positionId: position.id, |
|
|
level: +await network.level |
|
|
}) |
|
|
|
|
|
return { |
|
|
payload:{ |
|
|
username: user.username, |
|
|
userId: user.id.toString(), |
|
|
userRoleId: userRole.id.toString(), |
|
|
networkId: network.id.toString(), |
|
|
level:+network.level, |
|
|
roleId:role.id.toString(), |
|
|
include:[], |
|
|
exclude:[], |
|
|
positionId: position.id.toString(), |
|
|
timestamp: Date.now(), |
|
|
jwtId: getRandomNumber(1, 1e3), |
|
|
ip:getRandomIp(), |
|
|
agent:null, |
|
|
...preferedPayload |
|
|
}, |
|
|
network, |
|
|
role, |
|
|
position, |
|
|
userRole, |
|
|
user, |
|
|
networkPosition, |
|
|
positionRole |
|
|
} |
|
|
} |
|
|
const hash = (data) => |
|
|
crypto.createHash("sha256").update(JSON.stringify(data)).digest("hex"); |
|
|
async function createUser(data){ |
|
|
const newUser = await db.user.create({ |
|
|
firstName: `createUserTestName${getRandomNumber(1, 1e3)}`, |
|
|
lastName: `createUserTestLastName${getRandomNumber(1, 1e3)}`, |
|
|
nationalId: getRandomNumber(1e9, 9e9).toString(), |
|
|
username: `baseuser${getRandomNumber(1e6, 1e9-1)}`, |
|
|
cellphone: `0965${getRandomNumber(1e6, 9e6)}`, |
|
|
password: await bcrypt.hash("Password@123",10), |
|
|
hash:hash("Password@123"), |
|
|
status: 0, |
|
|
consecutiveLoginFailures:0, |
|
|
birthDate:`${getRandomNumber(1900, 1950)}/${getRandomNumber(1, 12)}/${getRandomNumber(1, 31)}`, |
|
|
...data |
|
|
}); |
|
|
return newUser; |
|
|
} |
|
|
|
|
|
function getRandomIp(){ |
|
|
return `${getRandomNumber(1, 254)}.${getRandomNumber(1, 254)}.${getRandomNumber(1, 254)}.${getRandomNumber(1, 254)}`; |
|
|
} |
|
|
|
|
|
function getRandomNumber(min, max) { |
|
|
return Math.floor(Math.random() * (max - min + 1)) + min; |
|
|
} |
|
|
|
|
|
function areArraysEqualUnordered(arr1, arr2) { |
|
|
if (arr1?.length !== arr2?.length) { |
|
|
return false; |
|
|
} |
|
|
return arr1.every(item => arr2.includes(item)) && |
|
|
arr2.every(item => arr1.includes(item)); |
|
|
} |
|
|
|
|
|
function getRandomElement(arr) { |
|
|
if (!Array.isArray(arr) || arr.length === 0) { |
|
|
throw new Error("آرایه معتبر نیست"); |
|
|
} |
|
|
const randomIndex = Math.floor(Math.random() * arr.length); |
|
|
return arr[randomIndex]; |
|
|
} |
|
|
|
|
|
module.exports = { |
|
|
sleep, |
|
|
convertStringToJson, |
|
|
isValidRegex, |
|
|
convertDaysToSeconds, |
|
|
isValidDateStrict, |
|
|
parseValue, |
|
|
isNumber, |
|
|
returnBooleanOfArguments, |
|
|
safeRegexFromString, |
|
|
isBoolean, |
|
|
checkingConditionTypes, |
|
|
checkingOptions, |
|
|
checkCondition, |
|
|
saveLargeObject, |
|
|
realPayloadGenerator, |
|
|
hash, |
|
|
createUser, |
|
|
getRandomIp, |
|
|
getRandomNumber, |
|
|
areArraysEqualUnordered, |
|
|
getRandomElement |
|
|
} |