From 6bb2c2fd1d2e018ef30e17ee59b2f8f147ffc319 Mon Sep 17 00:00:00 2001 From: celestora Date: Sat, 22 Feb 2025 17:52:29 +0200 Subject: [PATCH 1/7] fix(cli): Fix errors in fetch-ton cli command --- CLI/FetchToncoinTransactions.php | 3 +-- bootstrap.php | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CLI/FetchToncoinTransactions.php b/CLI/FetchToncoinTransactions.php index 9de2c998..8bffe030 100755 --- a/CLI/FetchToncoinTransactions.php +++ b/CLI/FetchToncoinTransactions.php @@ -17,7 +17,7 @@ define("NANOTON", 1000000000); class FetchToncoinTransactions extends Command { - private $images; + private $transactions; protected static $defaultName = "fetch-ton"; @@ -76,7 +76,6 @@ class FetchToncoinTransactions extends Command $header->writeln("Gonna up the balance of users"); foreach ($response["result"] as $transfer) { - $outputArray; preg_match('/' . OPENVK_ROOT_CONF["openvk"]["preferences"]["ton"]["regex"] . '/', $transfer["in_msg"]["message"], $outputArray); $userId = ctype_digit($outputArray[1]) ? intval($outputArray[1]) : null; if (is_null($userId)) { diff --git a/bootstrap.php b/bootstrap.php index 42241ca4..9100d3f2 100644 --- a/bootstrap.php +++ b/bootstrap.php @@ -443,7 +443,9 @@ function downloadable_name(string $text): string } return (function () { - _ovk_check_environment(); + if (php_sapi_name() != "cli") + _ovk_check_environment(); + require __DIR__ . "/vendor/autoload.php"; setlocale(LC_TIME, "POSIX"); From a8246f2fb1adafa74ba256f1d8156e33c0f99e0e Mon Sep 17 00:00:00 2001 From: celestora Date: Sat, 22 Feb 2025 17:52:41 +0200 Subject: [PATCH 2/7] Make openvkctl executable from cmd.exe too --- openvkctl.cmd | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 openvkctl.cmd diff --git a/openvkctl.cmd b/openvkctl.cmd new file mode 100644 index 00000000..926b63e8 --- /dev/null +++ b/openvkctl.cmd @@ -0,0 +1,2 @@ +@echo off +php openvkctl %* \ No newline at end of file From 60f6286bde976aefe74eb9e85c90928dec7f3a4a Mon Sep 17 00:00:00 2001 From: celestora Date: Sat, 22 Feb 2025 19:34:00 +0200 Subject: [PATCH 3/7] feat(install): Add db upgrade command --- CLI/FetchToncoinTransactions.php | 4 +- CLI/RebuildImagesCommand.php | 4 +- CLI/UpgradeCommand.php | 259 +++++++++++++++++++++++++++++++ install/init-migration-table.sql | 9 ++ openvkctl | 1 + 5 files changed, 275 insertions(+), 2 deletions(-) create mode 100644 CLI/UpgradeCommand.php create mode 100644 install/init-migration-table.sql diff --git a/CLI/FetchToncoinTransactions.php b/CLI/FetchToncoinTransactions.php index 8bffe030..76af7a08 100755 --- a/CLI/FetchToncoinTransactions.php +++ b/CLI/FetchToncoinTransactions.php @@ -23,7 +23,9 @@ class FetchToncoinTransactions extends Command public function __construct() { - $this->transactions = DatabaseConnection::i()->getContext()->table("cryptotransactions"); + $ctx = DatabaseConnection::i()->getContext(); + if (in_array("cryptotransactions", $ctx->getStructure()->getTables())) + $this->transactions = $ctx->table("cryptotransactions"); parent::__construct(); } diff --git a/CLI/RebuildImagesCommand.php b/CLI/RebuildImagesCommand.php index cbc9e49c..a75931dd 100644 --- a/CLI/RebuildImagesCommand.php +++ b/CLI/RebuildImagesCommand.php @@ -20,7 +20,9 @@ class RebuildImagesCommand extends Command public function __construct() { - $this->images = DatabaseConnection::i()->getContext()->table("photos"); + $ctx = DatabaseConnection::i()->getContext(); + if (in_array("photos", $ctx->getStructure()->getTables())) + $this->images = $ctx->table("photos"); parent::__construct(); } diff --git a/CLI/UpgradeCommand.php b/CLI/UpgradeCommand.php new file mode 100644 index 00000000..f1aa91d7 --- /dev/null +++ b/CLI/UpgradeCommand.php @@ -0,0 +1,259 @@ +db = DatabaseConnection::i()->getConnection(); + + parent::__construct(); + } + + protected function configure(): void + { + $this->setDescription("Upgrade OpenVK installation") + ->setHelp("This command upgrades database schema after OpenVK was updated") + ->addOption("quick", "Q", InputOption::VALUE_NEGATABLE, + "Don't display warning before migrating database", false) + ->addOption("repair", "R", InputOption::VALUE_NEGATABLE, + "Attempt to repair database schema if tables are missing", false) + ->addArgument("chandler", InputArgument::OPTIONAL, + "Location of Chandler installation"); + } + + protected function checkDatabaseReadiness(bool &$chandlerOk, bool &$ovkOk, bool &$migrationsOk): void + { + $tables = $this->db->query("SHOW TABLES")->fetchAll(); + $tables = array_map(fn ($x) => strtoupper($x->offsetGet(0)), $tables); + + $missingTables = array_diff($this->chandlerTables, $tables); + if (sizeof($missingTables) == 0) + $chandlerOk = true; + else if (sizeof($missingTables) == sizeof($this->chandlerTables)) + $chandlerOk = null; + else + $chandlerOk = false; + + $ovkOk = in_array("PROFILES", $tables); + $migrationsOk = in_array("OVK_UPGRADE_HISTORY", $tables); + } + + protected function executeSqlScript(int $errCode, string $script, SymfonyStyle $io, bool $transaction = false): int + { + $pdo = $this->db->getPdo(); + + $res = false; + try { + if ($transaction) + $res = $pdo->beginTransaction(); + + $res = $pdo->exec($script); + + if ($transaction) + $res = $pdo->commit(); + } catch (\PDOException $e) {} + + if ($res === false) + goto error; + + return 0; + + error: + $io->getErrorStyle()->error([ + "Failed to execute SQL statement:", + implode("\t", $pdo->errorInfo()) + ]); + + return $errCode; + } + + protected function getNextLevel(): int + { + $record = $this->db->query("SELECT level FROM ovk_upgrade_history ORDER BY level DESC LIMIT 1"); + if (!$record->getRowCount()) + return 0; + + return $record->fetchField() + 1; + } + + protected function getMigrationFiles(): array + { + $files = []; + $root = dirname(__DIR__ . "/../install/init-static-db.sql"); + + foreach (glob("$root/sqls/*.sql") as $file) + $files[(int) basename($file)] = basename($file); + + ksort($files); + + return $files; + } + + protected function installChandler(InputInterface $input, SymfonyStyle $io, bool $drop = false): int + { + $chandlerLocation = $input->getArgument("chandler") ?? (__DIR__ . "/../../../../"); + $chandlerConfigLocation = "$chandlerLocation/chandler.yml"; + + if (!file_exists($chandlerConfigLocation)) { + $err = ["Could not find chandler location. Perhaps your config is too unique?"]; + if (!$input->getOption("chandler")) + $err[] = "Specify absolute path to your chandler installation using the --chandler option."; + + $io->getErrorStyle()->error($err); + + return 21; + } + + if ($drop) { + $bar = new ProgressBar($io, sizeof($this->chandlerTables)); + $io->writeln("Dropping chandler tables..."); + + foreach ($bar->iterate($this->chandlerTables) as $table) + $this->db->query("DROP TABLE IF EXISTS $table;"); + + $io->newLine(); + } + + $installFile = file_get_contents("$chandlerLocation/install/init-db.sql"); + + return $this->executeSqlScript(22, $installFile, $io); + } + + protected function initSchema(SymfonyStyle $io): int + { + $installFile = file_get_contents(__DIR__ . "/../install/init-static-db.sql"); + + return $this->executeSqlScript(31, $installFile, $io); + } + + protected function initUpgradeLog(SymfonyStyle $io): int + { + $installFile = file_get_contents(__DIR__ . "/../install/init-migration-table.sql"); + + return $this->executeSqlScript(31, $installFile, $io); + } + + protected function runMigrations(SymfonyStyle $io): int + { + $nextLevel = $this->getNextLevel(); + $migrations = array_filter($this->getMigrationFiles(), fn ($id) => $id >= $nextLevel, ARRAY_FILTER_USE_KEY); + + if (!sizeof($migrations)) { + $io->writeln("Database up to date. Nothing left to do."); + + return 24; + } + + $uname = addslashes(`whoami`); + $bar = new ProgressBar($io, sizeof($migrations)); + $bar->setFormat("very_verbose"); + + foreach ($bar->iterate($migrations) as $num => $migration) { + $script = file_get_contents(__DIR__ . "/../install/sqls/$migration"); + $res = $this->executeSqlScript(100 + $num, $script, $io, true); + if ($res != 0) { + $io->getErrorStyle()->error("Error while executing migration №$num"); + + return $res; + } + + $t = time(); + $this->db->query("INSERT INTO ovk_upgrade_history VALUES ($num, $t, \"$uname\");"); + } + + return 0; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + if (!$input->getOption("quick")) { + $io->writeln("Do full backup of the database before executing this command!"); + $io->writeln("Command will resume execution after 5 seconds."); + $io->writeln("You can skip this warning with --quick option."); + sleep(5); + } + + $migrationsOk = false; + $chandlerOk = false; + $ovkOk = false; + + $this->checkDatabaseReadiness($chandlerOk, $ovkOk, $migrationsOk); + + $res = 0; + if ($chandlerOk === null) { + $io->writeln("Chandler schema not detected, attempting to install..."); + + $res = $this->installChandler($input, $io); + } else if ($chandlerOk === false) { + if ($input->getOption("repair")) { + $io->warning("Chandler schema detected but is broken, attempting to repair..."); + + $res = $this->installChandler($input, $io, true); + } else { + $io->writeln("Chandler schema detected but is broken"); + $io->writeln("Run command with --repair to repair (PERMISSIONS WILL BE LOST)"); + + return 1; + } + } + + if ($res > 0) + return $res; + + if (!$ovkOk) { + $io->writeln("Initializing OpenVK schema..."); + $res = $this->initSchema($io); + if ($res > 0) + return $res; + } + + if (!$migrationsOk) { + $io->writeln("Initializing upgrade log..."); + $res = $this->initUpgradeLog($io); + if ($res > 0) + return $res; + } + + $io->writeln("Upgrading database..."); + $res = $this->runMigrations($io); + + if (!$res) { + $io->success("Database has been upgraded!"); + + return 0; + } else if ($res != 24) { + return $res; + } + + return 0; + } +} \ No newline at end of file diff --git a/install/init-migration-table.sql b/install/init-migration-table.sql new file mode 100644 index 00000000..53edb638 --- /dev/null +++ b/install/init-migration-table.sql @@ -0,0 +1,9 @@ +START TRANSACTION; + +CREATE TABLE `ovk_upgrade_history` ( + `level` smallint UNSIGNED NOT NULL, + `timestamp` bigint(20) UNSIGNED NOT NULL, + `operator` varchar(256) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT "Maintenance Script" +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; + +COMMIT; \ No newline at end of file diff --git a/openvkctl b/openvkctl index 1ed5f3be..005de9c8 100755 --- a/openvkctl +++ b/openvkctl @@ -12,6 +12,7 @@ $bootstrap = require(__DIR__ . "/../../../chandler/Bootstrap.php"); $bootstrap->ignite(true); $application = new Application(); +$application->add(new CLI\UpgradeCommand()); $application->add(new CLI\RebuildImagesCommand()); $application->add(new CLI\FetchToncoinTransactions()); From 52f5ed4208bcb840404f2833fa999928df7cfad9 Mon Sep 17 00:00:00 2001 From: celestora Date: Sat, 22 Feb 2025 19:36:54 +0200 Subject: [PATCH 4/7] fix(install): Make install SQLs more compatible with autoupgrades --- install/sqls/00000-gifts.sql | 5 +---- install/sqls/00026-toncoin-fetching.sql | 4 ++-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/install/sqls/00000-gifts.sql b/install/sqls/00000-gifts.sql index 0ee54f14..59a5c46d 100644 --- a/install/sqls/00000-gifts.sql +++ b/install/sqls/00000-gifts.sql @@ -10,13 +10,10 @@ CREATE TABLE IF NOT EXISTS `coin_vouchers` ( `deleted` tinyint(1) NOT NULL DEFAULT '0' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -DELIMITER $$ CREATE TRIGGER `coinVoucherTokenAutoGen` BEFORE INSERT ON `coin_vouchers` FOR EACH ROW IF NEW.token IS NULL THEN SET NEW.token = SUBSTRING(UPPER(REPLACE(UUID(), "-", "")), 1, 24); -END IF -$$ -DELIMITER ; +END IF; CREATE TABLE IF NOT EXISTS `gifts` ( `id` bigint(20) unsigned NOT NULL, diff --git a/install/sqls/00026-toncoin-fetching.sql b/install/sqls/00026-toncoin-fetching.sql index 6de0c925..d88381ff 100644 --- a/install/sqls/00026-toncoin-fetching.sql +++ b/install/sqls/00026-toncoin-fetching.sql @@ -1,6 +1,6 @@ CREATE TABLE `cryptotransactions` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, - `hash` varchar(45) COLLATE utf8mb4_general_nopad_ci NOT NULL, + `hash` varchar(45) COLLATE utf8mb4_unicode_520_ci NOT NULL, `lt` bigint(20) NOT NULL, PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_nopad_ci; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; From b5b48538910cf8686a38c81e7892b847558e8c80 Mon Sep 17 00:00:00 2001 From: celestora Date: Sat, 22 Feb 2025 20:24:31 +0200 Subject: [PATCH 5/7] feat(install): Add --oneshot option to upgrade MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit скрутка на зиму (на когда установщик будет чтобы можно было аяксом теребить обновление БД в браузере без 504) --- CLI/UpgradeCommand.php | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/CLI/UpgradeCommand.php b/CLI/UpgradeCommand.php index f1aa91d7..afe861ad 100644 --- a/CLI/UpgradeCommand.php +++ b/CLI/UpgradeCommand.php @@ -44,6 +44,8 @@ class UpgradeCommand extends Command { "Don't display warning before migrating database", false) ->addOption("repair", "R", InputOption::VALUE_NEGATABLE, "Attempt to repair database schema if tables are missing", false) + ->addOption("oneshot", "O", InputOption::VALUE_NONE, + "Only execute one operation") ->addArgument("chandler", InputArgument::OPTIONAL, "Location of Chandler installation"); } @@ -160,7 +162,7 @@ class UpgradeCommand extends Command { return $this->executeSqlScript(31, $installFile, $io); } - protected function runMigrations(SymfonyStyle $io): int + protected function runMigrations(SymfonyStyle $io, bool $oneshot): int { $nextLevel = $this->getNextLevel(); $migrations = array_filter($this->getMigrationFiles(), fn ($id) => $id >= $nextLevel, ARRAY_FILTER_USE_KEY); @@ -186,6 +188,9 @@ class UpgradeCommand extends Command { $t = time(); $this->db->query("INSERT INTO ovk_upgrade_history VALUES ($num, $t, \"$uname\");"); + + if ($oneshot) + return 5; } return 0; @@ -193,6 +198,7 @@ class UpgradeCommand extends Command { protected function execute(InputInterface $input, OutputInterface $output): int { + $oneShotMode = $input->getOption("oneshot"); $io = new SymfonyStyle($input, $output); if (!$input->getOption("quick")) { @@ -208,7 +214,7 @@ class UpgradeCommand extends Command { $this->checkDatabaseReadiness($chandlerOk, $ovkOk, $migrationsOk); - $res = 0; + $res = -1; if ($chandlerOk === null) { $io->writeln("Chandler schema not detected, attempting to install..."); @@ -228,12 +234,16 @@ class UpgradeCommand extends Command { if ($res > 0) return $res; + else if ($res == 0 && $oneShotMode) + return 5; if (!$ovkOk) { $io->writeln("Initializing OpenVK schema..."); $res = $this->initSchema($io); if ($res > 0) return $res; + else if ($oneShotMode) + return 5; } if (!$migrationsOk) { @@ -241,10 +251,12 @@ class UpgradeCommand extends Command { $res = $this->initUpgradeLog($io); if ($res > 0) return $res; + else if ($oneShotMode) + return 5; } $io->writeln("Upgrading database..."); - $res = $this->runMigrations($io); + $res = $this->runMigrations($io, $oneShotMode); if (!$res) { $io->success("Database has been upgraded!"); From 22bae8a2e170dbea58eaf316b595ef4684881d31 Mon Sep 17 00:00:00 2001 From: celestora Date: Sat, 22 Feb 2025 20:45:17 +0200 Subject: [PATCH 6/7] feat(install): Make upgrade script also work for eventdb --- CLI/UpgradeCommand.php | 83 ++++++++++++++++++++++++-------- install/init-migration-table.sql | 6 +++ 2 files changed, 69 insertions(+), 20 deletions(-) diff --git a/CLI/UpgradeCommand.php b/CLI/UpgradeCommand.php index afe861ad..78792e0d 100644 --- a/CLI/UpgradeCommand.php +++ b/CLI/UpgradeCommand.php @@ -18,6 +18,7 @@ class UpgradeCommand extends Command { protected static $defaultName = "upgrade"; private Connection $db; + private Connection $eventDb; private array $chandlerTables = [ "CHANDLERACLPERMISSIONALIASES", @@ -32,6 +33,7 @@ class UpgradeCommand extends Command { public function __construct() { $this->db = DatabaseConnection::i()->getConnection(); + $this->eventDb = eventdb()->getConnection(); parent::__construct(); } @@ -50,7 +52,7 @@ class UpgradeCommand extends Command { "Location of Chandler installation"); } - protected function checkDatabaseReadiness(bool &$chandlerOk, bool &$ovkOk, bool &$migrationsOk): void + protected function checkDatabaseReadiness(bool &$chandlerOk, bool &$ovkOk, bool &$eventOk, bool &$migrationsOk): void { $tables = $this->db->query("SHOW TABLES")->fetchAll(); $tables = array_map(fn ($x) => strtoupper($x->offsetGet(0)), $tables); @@ -63,13 +65,22 @@ class UpgradeCommand extends Command { else $chandlerOk = false; + if (is_null($this->eventDb)) { + $eventOk = false; + } else if (is_null($this->eventDb->query("SHOW TABLES LIKE \"notifications\"")->fetch())) { + $eventOk = null; + } else { + $eventOk = true; + } + $ovkOk = in_array("PROFILES", $tables); $migrationsOk = in_array("OVK_UPGRADE_HISTORY", $tables); } - protected function executeSqlScript(int $errCode, string $script, SymfonyStyle $io, bool $transaction = false): int + protected function executeSqlScript(int $errCode, string $script, SymfonyStyle $io, bool $transaction = false, + bool $eventDb = false): int { - $pdo = $this->db->getPdo(); + $pdo = ($eventDb ? $this->eventDb : $this->db)->getPdo(); $res = false; try { @@ -96,21 +107,23 @@ class UpgradeCommand extends Command { return $errCode; } - protected function getNextLevel(): int + protected function getNextLevel(bool $eventDb = false): int { - $record = $this->db->query("SELECT level FROM ovk_upgrade_history ORDER BY level DESC LIMIT 1"); + $tbl = $eventDb ? "ovk_events_upgrade_history" : "ovk_upgrade_history"; + $record = $this->db->query("SELECT level FROM $tbl ORDER BY level DESC LIMIT 1"); if (!$record->getRowCount()) return 0; return $record->fetchField() + 1; } - protected function getMigrationFiles(): array + protected function getMigrationFiles(bool $eventDb = false): array { $files = []; $root = dirname(__DIR__ . "/../install/init-static-db.sql"); + $dir = $eventDb ? "sqls/eventdb" : "sqls"; - foreach (glob("$root/sqls/*.sql") as $file) + foreach (glob("$root/$dir/*.sql") as $file) $files[(int) basename($file)] = basename($file); ksort($files); @@ -155,31 +168,38 @@ class UpgradeCommand extends Command { return $this->executeSqlScript(31, $installFile, $io); } + protected function initEventSchema(SymfonyStyle $io): int + { + $installFile = file_get_contents(__DIR__ . "/../install/init-event-db.sql"); + + return $this->executeSqlScript(31, $installFile, $io, true, true); + } + protected function initUpgradeLog(SymfonyStyle $io): int { $installFile = file_get_contents(__DIR__ . "/../install/init-migration-table.sql"); - return $this->executeSqlScript(31, $installFile, $io); + return $this->executeSqlScript(31, $installFile, $io, true); } - protected function runMigrations(SymfonyStyle $io, bool $oneshot): int + protected function runMigrations(SymfonyStyle $io, bool $eventDb, bool $oneshot): int { - $nextLevel = $this->getNextLevel(); - $migrations = array_filter($this->getMigrationFiles(), fn ($id) => $id >= $nextLevel, ARRAY_FILTER_USE_KEY); + $dir = $eventDb ? "sqls/eventdb" : "sqls"; + $tbl = $eventDb ? "ovk_events_upgrade_history" : "ovk_upgrade_history"; + $nextLevel = $this->getNextLevel($eventDb); + $migrations = array_filter($this->getMigrationFiles($eventDb), fn ($id) => $id >= $nextLevel, + ARRAY_FILTER_USE_KEY); - if (!sizeof($migrations)) { - $io->writeln("Database up to date. Nothing left to do."); - + if (!sizeof($migrations)) return 24; - } $uname = addslashes(`whoami`); $bar = new ProgressBar($io, sizeof($migrations)); $bar->setFormat("very_verbose"); foreach ($bar->iterate($migrations) as $num => $migration) { - $script = file_get_contents(__DIR__ . "/../install/sqls/$migration"); - $res = $this->executeSqlScript(100 + $num, $script, $io, true); + $script = file_get_contents(__DIR__ . "/../install/$dir/$migration"); + $res = $this->executeSqlScript(100 + $num, $script, $io, true, $eventDb); if ($res != 0) { $io->getErrorStyle()->error("Error while executing migration №$num"); @@ -187,12 +207,14 @@ class UpgradeCommand extends Command { } $t = time(); - $this->db->query("INSERT INTO ovk_upgrade_history VALUES ($num, $t, \"$uname\");"); + $this->db->query("INSERT INTO $tbl VALUES ($num, $t, \"$uname\");"); if ($oneshot) return 5; } + $io->newLine(); + return 0; } @@ -210,9 +232,10 @@ class UpgradeCommand extends Command { $migrationsOk = false; $chandlerOk = false; + $eventOk = false; $ovkOk = false; - $this->checkDatabaseReadiness($chandlerOk, $ovkOk, $migrationsOk); + $this->checkDatabaseReadiness($chandlerOk, $ovkOk, $eventOk, $migrationsOk); $res = -1; if ($chandlerOk === null) { @@ -255,8 +278,26 @@ class UpgradeCommand extends Command { return 5; } + if ($eventOk !== false) { + if ($eventOk === null) { + $io->writeln("Initializing event database..."); + $res = $this->initEventSchema($io); + if ($res > 0) + return $res; + else if ($oneShotMode) + return 5; + } + + $io->writeln("Upgrading event database..."); + $res = $this->runMigrations($io, true, $oneShotMode); + if ($res == 24) + $output->writeln("Event database already up to date."); + else if ($res > 0) + return $res; + } + $io->writeln("Upgrading database..."); - $res = $this->runMigrations($io, $oneShotMode); + $res = $this->runMigrations($io, false, $oneShotMode); if (!$res) { $io->success("Database has been upgraded!"); @@ -266,6 +307,8 @@ class UpgradeCommand extends Command { return $res; } + $io->writeln("Database up to date. Nothing left to do."); + return 0; } } \ No newline at end of file diff --git a/install/init-migration-table.sql b/install/init-migration-table.sql index 53edb638..e26afcbb 100644 --- a/install/init-migration-table.sql +++ b/install/init-migration-table.sql @@ -6,4 +6,10 @@ CREATE TABLE `ovk_upgrade_history` ( `operator` varchar(256) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT "Maintenance Script" ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; +CREATE TABLE `ovk_events_upgrade_history` ( + `level` smallint UNSIGNED NOT NULL, + `timestamp` bigint(20) UNSIGNED NOT NULL, + `operator` varchar(256) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT "Maintenance Script" +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; + COMMIT; \ No newline at end of file From 0b010d8c56e17254f1551e6583e882c26674056e Mon Sep 17 00:00:00 2001 From: celestora Date: Sat, 22 Feb 2025 20:51:43 +0200 Subject: [PATCH 7/7] chore(install): Fix formatting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ой --- CLI/FetchToncoinTransactions.php | 3 +- CLI/RebuildImagesCommand.php | 3 +- CLI/UpgradeCommand.php | 259 ++++++++++++++++++------------- bootstrap.php | 5 +- 4 files changed, 157 insertions(+), 113 deletions(-) diff --git a/CLI/FetchToncoinTransactions.php b/CLI/FetchToncoinTransactions.php index 76af7a08..171ea091 100755 --- a/CLI/FetchToncoinTransactions.php +++ b/CLI/FetchToncoinTransactions.php @@ -24,8 +24,9 @@ class FetchToncoinTransactions extends Command public function __construct() { $ctx = DatabaseConnection::i()->getContext(); - if (in_array("cryptotransactions", $ctx->getStructure()->getTables())) + if (in_array("cryptotransactions", $ctx->getStructure()->getTables())) { $this->transactions = $ctx->table("cryptotransactions"); + } parent::__construct(); } diff --git a/CLI/RebuildImagesCommand.php b/CLI/RebuildImagesCommand.php index a75931dd..6340743c 100644 --- a/CLI/RebuildImagesCommand.php +++ b/CLI/RebuildImagesCommand.php @@ -21,8 +21,9 @@ class RebuildImagesCommand extends Command public function __construct() { $ctx = DatabaseConnection::i()->getContext(); - if (in_array("photos", $ctx->getStructure()->getTables())) + if (in_array("photos", $ctx->getStructure()->getTables())) { $this->images = $ctx->table("photos"); + } parent::__construct(); } diff --git a/CLI/UpgradeCommand.php b/CLI/UpgradeCommand.php index 78792e0d..f0ef8f5d 100644 --- a/CLI/UpgradeCommand.php +++ b/CLI/UpgradeCommand.php @@ -14,12 +14,13 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; -class UpgradeCommand extends Command { +class UpgradeCommand extends Command +{ protected static $defaultName = "upgrade"; - + private Connection $db; private Connection $eventDb; - + private array $chandlerTables = [ "CHANDLERACLPERMISSIONALIASES", "CHANDLERACLGROUPSPERMISSIONS", @@ -27,288 +28,328 @@ class UpgradeCommand extends Command { "CHANDLERACLRELATIONS", "CHANDLERGROUPS", "CHANDLERTOKENS", - "CHANDLERUSERS" + "CHANDLERUSERS", ]; - + public function __construct() { $this->db = DatabaseConnection::i()->getConnection(); $this->eventDb = eventdb()->getConnection(); - + parent::__construct(); } - + protected function configure(): void { $this->setDescription("Upgrade OpenVK installation") ->setHelp("This command upgrades database schema after OpenVK was updated") - ->addOption("quick", "Q", InputOption::VALUE_NEGATABLE, - "Don't display warning before migrating database", false) - ->addOption("repair", "R", InputOption::VALUE_NEGATABLE, - "Attempt to repair database schema if tables are missing", false) - ->addOption("oneshot", "O", InputOption::VALUE_NONE, - "Only execute one operation") - ->addArgument("chandler", InputArgument::OPTIONAL, - "Location of Chandler installation"); + ->addOption( + "quick", + "Q", + InputOption::VALUE_NEGATABLE, + "Don't display warning before migrating database", + false + ) + ->addOption( + "repair", + "R", + InputOption::VALUE_NEGATABLE, + "Attempt to repair database schema if tables are missing", + false + ) + ->addOption( + "oneshot", + "O", + InputOption::VALUE_NONE, + "Only execute one operation" + ) + ->addArgument( + "chandler", + InputArgument::OPTIONAL, + "Location of Chandler installation" + ); } - + protected function checkDatabaseReadiness(bool &$chandlerOk, bool &$ovkOk, bool &$eventOk, bool &$migrationsOk): void { $tables = $this->db->query("SHOW TABLES")->fetchAll(); - $tables = array_map(fn ($x) => strtoupper($x->offsetGet(0)), $tables); - + $tables = array_map(fn($x) => strtoupper($x->offsetGet(0)), $tables); + $missingTables = array_diff($this->chandlerTables, $tables); - if (sizeof($missingTables) == 0) + if (sizeof($missingTables) == 0) { $chandlerOk = true; - else if (sizeof($missingTables) == sizeof($this->chandlerTables)) + } elseif (sizeof($missingTables) == sizeof($this->chandlerTables)) { $chandlerOk = null; - else + } else { $chandlerOk = false; - + } + if (is_null($this->eventDb)) { $eventOk = false; - } else if (is_null($this->eventDb->query("SHOW TABLES LIKE \"notifications\"")->fetch())) { + } elseif (is_null($this->eventDb->query("SHOW TABLES LIKE \"notifications\"")->fetch())) { $eventOk = null; } else { $eventOk = true; } - + $ovkOk = in_array("PROFILES", $tables); $migrationsOk = in_array("OVK_UPGRADE_HISTORY", $tables); } - - protected function executeSqlScript(int $errCode, string $script, SymfonyStyle $io, bool $transaction = false, - bool $eventDb = false): int - { + + protected function executeSqlScript( + int $errCode, + string $script, + SymfonyStyle $io, + bool $transaction = false, + bool $eventDb = false + ): int { $pdo = ($eventDb ? $this->eventDb : $this->db)->getPdo(); - + $res = false; try { - if ($transaction) + if ($transaction) { $res = $pdo->beginTransaction(); - + } + $res = $pdo->exec($script); - - if ($transaction) + + if ($transaction) { $res = $pdo->commit(); - } catch (\PDOException $e) {} - - if ($res === false) + } + } catch (\PDOException $e) { + } + + if ($res === false) { goto error; - + } + return 0; - + error: $io->getErrorStyle()->error([ "Failed to execute SQL statement:", - implode("\t", $pdo->errorInfo()) + implode("\t", $pdo->errorInfo()), ]); - + return $errCode; } - + protected function getNextLevel(bool $eventDb = false): int { $tbl = $eventDb ? "ovk_events_upgrade_history" : "ovk_upgrade_history"; $record = $this->db->query("SELECT level FROM $tbl ORDER BY level DESC LIMIT 1"); - if (!$record->getRowCount()) + if (!$record->getRowCount()) { return 0; - + } + return $record->fetchField() + 1; } - + protected function getMigrationFiles(bool $eventDb = false): array { $files = []; $root = dirname(__DIR__ . "/../install/init-static-db.sql"); $dir = $eventDb ? "sqls/eventdb" : "sqls"; - - foreach (glob("$root/$dir/*.sql") as $file) + + foreach (glob("$root/$dir/*.sql") as $file) { $files[(int) basename($file)] = basename($file); - + } + ksort($files); - + return $files; } - + protected function installChandler(InputInterface $input, SymfonyStyle $io, bool $drop = false): int { $chandlerLocation = $input->getArgument("chandler") ?? (__DIR__ . "/../../../../"); $chandlerConfigLocation = "$chandlerLocation/chandler.yml"; - + if (!file_exists($chandlerConfigLocation)) { $err = ["Could not find chandler location. Perhaps your config is too unique?"]; - if (!$input->getOption("chandler")) + if (!$input->getOption("chandler")) { $err[] = "Specify absolute path to your chandler installation using the --chandler option."; - + } + $io->getErrorStyle()->error($err); - + return 21; } - + if ($drop) { $bar = new ProgressBar($io, sizeof($this->chandlerTables)); $io->writeln("Dropping chandler tables..."); - - foreach ($bar->iterate($this->chandlerTables) as $table) + + foreach ($bar->iterate($this->chandlerTables) as $table) { $this->db->query("DROP TABLE IF EXISTS $table;"); - + } + $io->newLine(); } - + $installFile = file_get_contents("$chandlerLocation/install/init-db.sql"); - + return $this->executeSqlScript(22, $installFile, $io); } - + protected function initSchema(SymfonyStyle $io): int { $installFile = file_get_contents(__DIR__ . "/../install/init-static-db.sql"); - + return $this->executeSqlScript(31, $installFile, $io); } - + protected function initEventSchema(SymfonyStyle $io): int { $installFile = file_get_contents(__DIR__ . "/../install/init-event-db.sql"); - + return $this->executeSqlScript(31, $installFile, $io, true, true); } - + protected function initUpgradeLog(SymfonyStyle $io): int { $installFile = file_get_contents(__DIR__ . "/../install/init-migration-table.sql"); - + return $this->executeSqlScript(31, $installFile, $io, true); } - + protected function runMigrations(SymfonyStyle $io, bool $eventDb, bool $oneshot): int { $dir = $eventDb ? "sqls/eventdb" : "sqls"; $tbl = $eventDb ? "ovk_events_upgrade_history" : "ovk_upgrade_history"; $nextLevel = $this->getNextLevel($eventDb); - $migrations = array_filter($this->getMigrationFiles($eventDb), fn ($id) => $id >= $nextLevel, - ARRAY_FILTER_USE_KEY); - - if (!sizeof($migrations)) + $migrations = array_filter( + $this->getMigrationFiles($eventDb), + fn($id) => $id >= $nextLevel, + ARRAY_FILTER_USE_KEY + ); + + if (!sizeof($migrations)) { return 24; - + } + $uname = addslashes(`whoami`); $bar = new ProgressBar($io, sizeof($migrations)); $bar->setFormat("very_verbose"); - + foreach ($bar->iterate($migrations) as $num => $migration) { $script = file_get_contents(__DIR__ . "/../install/$dir/$migration"); $res = $this->executeSqlScript(100 + $num, $script, $io, true, $eventDb); if ($res != 0) { $io->getErrorStyle()->error("Error while executing migration №$num"); - + return $res; } - + $t = time(); $this->db->query("INSERT INTO $tbl VALUES ($num, $t, \"$uname\");"); - - if ($oneshot) + + if ($oneshot) { return 5; + } } - + $io->newLine(); - + return 0; } - + protected function execute(InputInterface $input, OutputInterface $output): int { $oneShotMode = $input->getOption("oneshot"); $io = new SymfonyStyle($input, $output); - + if (!$input->getOption("quick")) { $io->writeln("Do full backup of the database before executing this command!"); $io->writeln("Command will resume execution after 5 seconds."); $io->writeln("You can skip this warning with --quick option."); sleep(5); } - + $migrationsOk = false; $chandlerOk = false; $eventOk = false; $ovkOk = false; - + $this->checkDatabaseReadiness($chandlerOk, $ovkOk, $eventOk, $migrationsOk); - + $res = -1; if ($chandlerOk === null) { $io->writeln("Chandler schema not detected, attempting to install..."); - + $res = $this->installChandler($input, $io); - } else if ($chandlerOk === false) { + } elseif ($chandlerOk === false) { if ($input->getOption("repair")) { $io->warning("Chandler schema detected but is broken, attempting to repair..."); - + $res = $this->installChandler($input, $io, true); } else { $io->writeln("Chandler schema detected but is broken"); $io->writeln("Run command with --repair to repair (PERMISSIONS WILL BE LOST)"); - + return 1; } } - - if ($res > 0) + + if ($res > 0) { return $res; - else if ($res == 0 && $oneShotMode) + } elseif ($res == 0 && $oneShotMode) { return 5; - + } + if (!$ovkOk) { $io->writeln("Initializing OpenVK schema..."); $res = $this->initSchema($io); - if ($res > 0) + if ($res > 0) { return $res; - else if ($oneShotMode) + } elseif ($oneShotMode) { return 5; + } } - + if (!$migrationsOk) { $io->writeln("Initializing upgrade log..."); $res = $this->initUpgradeLog($io); - if ($res > 0) + if ($res > 0) { return $res; - else if ($oneShotMode) + } elseif ($oneShotMode) { return 5; + } } - + if ($eventOk !== false) { if ($eventOk === null) { $io->writeln("Initializing event database..."); $res = $this->initEventSchema($io); - if ($res > 0) + if ($res > 0) { return $res; - else if ($oneShotMode) + } elseif ($oneShotMode) { return 5; + } } - + $io->writeln("Upgrading event database..."); $res = $this->runMigrations($io, true, $oneShotMode); - if ($res == 24) + if ($res == 24) { $output->writeln("Event database already up to date."); - else if ($res > 0) + } elseif ($res > 0) { return $res; + } } - + $io->writeln("Upgrading database..."); $res = $this->runMigrations($io, false, $oneShotMode); - + if (!$res) { $io->success("Database has been upgraded!"); - + return 0; - } else if ($res != 24) { + } elseif ($res != 24) { return $res; } - + $io->writeln("Database up to date. Nothing left to do."); - + return 0; } -} \ No newline at end of file +} diff --git a/bootstrap.php b/bootstrap.php index 9100d3f2..21fdc65b 100644 --- a/bootstrap.php +++ b/bootstrap.php @@ -443,9 +443,10 @@ function downloadable_name(string $text): string } return (function () { - if (php_sapi_name() != "cli") + if (php_sapi_name() != "cli") { _ovk_check_environment(); - + } + require __DIR__ . "/vendor/autoload.php"; setlocale(LC_TIME, "POSIX");