From f56f2c8338897116bfef6dc547ad82bacc6383d8 Mon Sep 17 00:00:00 2001 From: Backup user agent Date: Fri, 6 Jun 2025 13:59:54 +0400 Subject: [PATCH] init --- .env.sample.php | 23 +++++ .gitignore | 9 ++ DbContinuousIntegration.php | 10 ++ DbContinuousIntegrationWrapper.sh | 9 ++ Makefile | 10 ++ app.php | 23 +++++ composer.json | 5 + configure | 27 +++++ install/SYS_PRD_BND.Composer.sql | 10 ++ install/SYS_PRD_BND.Tables.sql | 9 ++ plt.php | 165 ++++++++++++++++++++++++++++++ sys.php | 68 ++++++++++++ 12 files changed, 368 insertions(+) create mode 100644 .env.sample.php create mode 100644 .gitignore create mode 100755 DbContinuousIntegration.php create mode 100755 DbContinuousIntegrationWrapper.sh create mode 100644 Makefile create mode 100755 app.php create mode 100644 composer.json create mode 100755 configure create mode 100644 install/SYS_PRD_BND.Composer.sql create mode 100644 install/SYS_PRD_BND.Tables.sql create mode 100755 plt.php create mode 100755 sys.php 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; +} +