commit f56f2c8338897116bfef6dc547ad82bacc6383d8 Author: Backup user agent Date: Fri Jun 6 13:59:54 2025 +0400 init diff --git a/.env.sample.php b/.env.sample.php new file mode 100644 index 0000000..8dc3dd0 --- /dev/null +++ b/.env.sample.php @@ -0,0 +1,23 @@ + "12345678", +]; + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6fdde9b --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +vendor/ +composer.lock +composer.sync + + +.env +.env.php + +test_code.php diff --git a/DbContinuousIntegration.php b/DbContinuousIntegration.php new file mode 100755 index 0000000..3d3a239 --- /dev/null +++ b/DbContinuousIntegration.php @@ -0,0 +1,10 @@ +&1 > $@ + +install: + echo "Add this line to crontab -e" + echo '@reboot /bin/bash /root/DbContinuousIntegration/DbContinuousIntegrationWrapper.sh 2> /dev/null > /dev/null &' + echo + echo 'CREATE TABLE SYS_PRD_BND.Tables (Name, onUpdate_phpCode, onUpdate_pyCode, LastUpdated);' | sudo mysql diff --git a/app.php b/app.php new file mode 100755 index 0000000..1c639cb --- /dev/null +++ b/app.php @@ -0,0 +1,23 @@ + .env.php + '$LastUpdated'") as $unprocessedRow) { + echo "Found row\n".json_encode($unprocessedRow)."\n"; + $functionName = "handleNew".str_replace(".","__",$activeTable["Name"])."Row"; + + echo "Checking if function ".greenText($functionName)." exists at code-level (sys).\n"; + if (function_exists($functionName)) { + echo "Calling function ".greenText("$functionName(...)")."\n"; + if ($functionName($unprocessedRow, $error) === false) { + sql("UPDATE SYS_PRD_BND.Tables SET LastError = '".str_replace("'",'"',$error)."', LastUpdated = NOW() WHERE Name = '{$activeTable["Name"]}'"); + sendTelegramMessage("*ERROR* on trigger function for table {$activeTable["Name"]} : ".$error,"DatabaseGroup"); + } else { + sql("UPDATE SYS_PRD_BND.Tables SET LastError = '', LastUpdated = NOW() WHERE Name = '{$activeTable["Name"]}'"); + } + + } + /* + * PHP CODE + */ + echo "Checking if function ".greenText($functionName)." exists in PHP at database-level (app).\n"; + if (!is_null($onUpdate_phpCode) && !empty($onUpdate_phpCode)) { + echo "Creating function ".greenText("$functionName(...)")." context\n"; + // 1. Add the PHP START TAG + $code = "<"."?"."php \n"; + // 2. Add the CONSTANTS + foreach(sql("SELECT Name, Type, Value FROM SYS_PRD_BND.Constants") as $const) + $code .= "define(\"{$const["Name"]}\",".($const["Type"]!="String"?$const["Value"]:'"'.$const["Value"].'"').");\n"; + // 3. Add the SUPPORT FUNCTIONS + foreach(sql("SELECT Name, InputArgs_json, PhpCode FROM SYS_PRD_BND.SupportFunctions WHERE PhpCode IS NOT NULL") as $f) + $code .= "function {$f["Name"]} (".implode(", ",array_map(fn($s)=>"\$$s",array_keys(json_decode($f["InputArgs_json"],1)))).") {\n".$f["PhpCode"]."\n}\n"; + + $code .= "require_once '/root/DbContinuousIntegration/sys.php'; \n"; + $code .= "function $functionName (&\$data, &\$error) {\n"; + $code .= $onUpdate_phpCode; + $code .= "\n}\n"; + $code .= "\$data = ".var_export($unprocessedRow,1).";\n"; + $code .= "\$initial_data = json_encode(".var_export($unprocessedRow,1).");\n"; + $code .= "$functionName(\$data,\$error);"; + $code .= "\necho json_encode(\$data);\n"; + file_put_contents(__DIR__."/test_code.php", $code); + echo "Running function ".greenText("$functionName(...)")." in sandbox environment\n"; + if (runProcess("/usr/bin/php",$code,$stdout,$error) != 0) { + sql("UPDATE SYS_PRD_BND.Tables SET LastError = '".str_replace("'",'"',$error)."', LastUpdated = NOW() WHERE Name = '{$activeTable["Name"]}'"); + sendTelegramMessage("*ERROR* on trigger function for table {$activeTable["Name"]} : ".$error,"DatabaseGroup"); + } else { + // Update database row if needed + $newRowValue = json_decode($stdout,1); + echo "\n".redText("DEBUG new-row-value-json: ").json_encode($newRowValue)."\n"; + echo "\n".redText("DEBUG unprocessedRow-json: ").json_encode($unprocessedRow)."\n"; + if (json_encode($newRowValue) != json_encode($unprocessedRow)) { + $pkColsName = getTblPrimaryKeyColName($activeTable["Name"]); + $pkColsValues = array_map(fn($cName)=>$unprocessedRow[$cName],$pkColsName); + $sql_instruction = ""; + $sql_instruction .= "UPDATE "; + $sql_instruction .= $activeTable["Name"]; + $sql_instruction .= " SET ".implode(",",array_map(fn($k,$v)=>"$k=".(is_numeric($v)?$v:"'".str_replace("'","''",$v)."'"),array_keys($newRowValue),array_values($newRowValue))); + $sql_instruction .= " WHERE ".implode(" AND ",array_map(fn($k,$v)=>"$k = ".(is_numeric($newRowValue[$k])?$newRowValue[$k]:'"'.$newRowValue[$k].'"'),$pkColsName,$pkColsValues)); + echo "\n".redText("DEBUG sql-instruction: ").$sql_instruction."\n"; + sql($sql_instruction); + } + // Update the status of the operation + sql("UPDATE SYS_PRD_BND.Tables SET LastError = '', LastUpdated = NOW() WHERE Name = '{$activeTable["Name"]}'"); + } + + } + /* + * Python CODE + */ + echo "Checking if function ".greenText($functionName)." exists in Python at database-level (app).\n"; + if (!is_null($onUpdate_pyCode) && !empty($onUpdate_pyCode)) { + echo "Creating function ".greenText("$functionName(...)")." context\n"; + $code = ""; + // 1. Insert the import to all the external libraries + foreach(sql("SELECT LibName, AliasName FROM SYS_PRD_BND.PyPi") as $module) + $code .= "import ".$module["LibName"] .(is_null($module["AliasName"])&&!empty($module["AliasName"])?" as ".$module["AliasName"]:"")."\n\n"; + + // 2. Define the handler function + $code .= "def $functionName (data, error) :\n"; + $code .= " ".implode("\n ",explode("\n",$onUpdate_pyCode))."\n"; + + // 3. Pass the data + $code .= "data = ".json_encode($unprocessedRow)."\n"; + $code .= "error = { \"status\": \"ok\", \"message\": \"\"}\n"; + + // 4. Call the handler function + $code .= "$functionName(data, error)\n"; + + // 5. Data changing code + $code .= "\nprint(json.dumps(data))\n"; + + file_put_contents(__DIR__."/test_code.py",$code); + echo "Running function ".greenText("$functionName(...)")." in sandbox python environment\n"; + if (runProcess("/usr/bin/python3",$code,$stdout,$error) != 0) { + sql("UPDATE SYS_PRD_BND.Tables SET LastError = '".str_replace("'",'"',$error)."', LastUpdated = NOW() WHERE Name = '{$activeTable["Name"]}'"); + sendTelegramMessage("*ERROR* on trigger function for table {$activeTable["Name"]} : ".$error,"DatabaseGroup"); + break; + } else { + // Update database row if needed + $newRowValue = json_decode($stdout,1); + echo "\n".redText("DEBUG new-row-value-json: ").json_encode($newRowValue)."\n"; + echo "\n".redText("DEBUG unprocessedRow-json: ").json_encode($unprocessedRow)."\n"; + if (json_encode($newRowValue) != json_encode($unprocessedRow)) { + $pkColsName = getTblPrimaryKeyColName($activeTable["Name"]); + $pkColsValues = array_map(fn($cName)=>$unprocessedRow[$cName],$pkColsName); + $sql_instruction = ""; + $sql_instruction .= "UPDATE "; + $sql_instruction .= $activeTable["Name"]; + $sql_instruction .= " SET ".implode(",",array_map(fn($k,$v)=>"$k=".(is_numeric($v)?$v:"\"$v\""),array_keys($newRowValue),array_values($newRowValue))); + $sql_instruction .= " WHERE ".implode(" AND ",array_map(fn($k,$v)=>"$k = ".(is_numeric($newRowValue[$k])?$newRowValue[$k]:'"'.$newRowValue[$k].'"'),$pkColsName,$pkColsValues)); + echo "\n".redText("DEBUG sql-instruction: ").$sql_instruction."\n"; + sql($sql_instruction); + } + // Update the status of the operation + sql("UPDATE SYS_PRD_BND.Tables SET LastError = '', LastUpdated = NOW() WHERE Name = '{$activeTable["Name"]}'"); + } + + } + } + } +} +function greenText($string) { $green = "\033[0;32m"; $reset = "\033[0m"; return $green . $string . $reset ; } +function blueText($string) { $green = "\033[0;34m"; $reset = "\033[0m"; return $green . $string . $reset ; } +function redText($string) { $green = "\033[0;31m"; $reset = "\033[0m"; return $green . $string . $reset ; } +function sendTelegramMessage($message, $dstUsers) { + global $BOT_TOKEN, $CHAT_IDS; + + if (is_string($dstUsers)) $dstUsers = [$dstUsers]; + + foreach($dstUsers as $dstUser) { + if (!isset($CHAT_IDS[$dstUser])) + continue; + else + $CHAT_ID = $CHAT_IDS[$dstUser]; + + + $JSON_RAW_DATA = json_encode([ + 'chat_id' => $CHAT_ID, + 'text' => $message, + 'parse_mode' => 'markdown' + ]); + + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, "https://api.telegram.org/bot$BOT_TOKEN/sendMessage"); + curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: application/json')); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $JSON_RAW_DATA); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + + $response = curl_exec($curl); + //print_r($response); + curl_close($curl); + } +} diff --git a/sys.php b/sys.php new file mode 100755 index 0000000..6fd2d93 --- /dev/null +++ b/sys.php @@ -0,0 +1,68 @@ +prepare($query); + if ($stmt->execute() === false) return ["status" => "error", "query"=>$query]; + return $stmt->fetchAll(PDO::FETCH_ASSOC); +} +function sql_write($query) { + global $db; + if (!isset($db) || is_null($db)) $db = new PDO("mysql:host=".DB_HOST.";dbname=".DB_NAME,DB_USER,DB_PASS); + $stmt = $db->prepare($query); + if ($stmt->execute() === false) die ("sql error: $query"); + return $stmt->rowCount(); +} +function sql($query) { + if (strpos($query, "SELECT ") === 0) return sql_read($query); + else return sql_write($query); +} + + +function runProcess($cmd, $stdin, &$stdout, &$stderr) { + $descriptorspec = array( + 0 => array("pipe", "r"), // stdin is a pipe that the child will read from + 1 => array("pipe", "w"), // stdout is a pipe that the child will write to + 2 => array("pipe", "w") // stderr is a pipe that the child will write to + ); + + $process = proc_open($cmd, $descriptorspec, $pipes); + + if (is_resource($process)) { + // Write to stdin and close it + fwrite($pipes[0], $stdin); + fclose($pipes[0]); + + // Read the output of the command + $stdout = stream_get_contents($pipes[1]); + fclose($pipes[1]); + + // Read the error output of the command + $stderr = stream_get_contents($pipes[2]); + fclose($pipes[2]); + + // It's important to close all pipes before calling proc_close in order to avoid a deadlock + $return_value = proc_close($process); + + return $return_value; + } else { + // Return an error code if the process could not be started + return -1; + } +} +function getTblPrimaryKeyColName($tblName) { + $dbName = explode(".",$tblName)[0]; + $tblName = explode(".",$tblName)[1]; + + $cols = []; + foreach(sql("SELECT COLUMN_NAME PksColName FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = '$dbName' AND TABLE_NAME = '$tblName' AND CONSTRAINT_NAME = 'PRIMARY';") as $row) + $cols[] = $row["PksColName"]; + return $cols; +} +