From f50053a745e4537af400f8aafac3ba1a3c4a6b85 Mon Sep 17 00:00:00 2001 From: Anonymous Contributor Date: Sat, 9 Sep 2023 06:21:30 +0200 Subject: [PATCH] Add migration feature for own MySQL tables --- app/Mcp.php | 12 +++- app/MigrationManager.php | 119 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 app/MigrationManager.php diff --git a/app/Mcp.php b/app/Mcp.php index 2999220..4c8fd5b 100644 --- a/app/Mcp.php +++ b/app/Mcp.php @@ -3,8 +3,8 @@ declare(strict_types=1); namespace Mcp; -use PDO; use Exception; +use PDO; class Mcp implements ConnectionProvider { @@ -52,7 +52,14 @@ class Mcp implements ConnectionProvider $this->config = array_merge($config, $this->config); } catch (Exception $e) { error_log('Could not load config, aborting. Error: '.$e->getMessage()); - http_response_code(500); + http_response_code(503); + exit(); + } + + $migrate = new MigrationManager($basedir.DIRECTORY_SEPARATOR.'data'.DIRECTORY_SEPARATOR.'migrate_ver'); + if (!$migrate->isMigrated() && !$migrate->migrate($this->db())) { + error_log('Migration to latest DB structure version failed, aborting.'); + http_response_code(503); exit(); } } @@ -112,5 +119,4 @@ class Mcp implements ConnectionProvider (new $reqClass($this))->handleRequest(); } - } diff --git a/app/MigrationManager.php b/app/MigrationManager.php new file mode 100644 index 0000000..4891300 --- /dev/null +++ b/app/MigrationManager.php @@ -0,0 +1,119 @@ + [ + 'RENAME TABLE IF EXISTS UserIdentitys TO mcp_user_identities, PasswordResetTokens TO mcp_password_reset, InviteCodes TO mcp_invites, im_offline_send TO mcp_offlineim_send, regions_info TO mcp_regions_info', + 'ALTER TABLE mcp_invites MODIFY COLUMN InviteCode CHAR(64) NOT NULL', + 'ALTER TABLE mcp_regions_info MODIFY COLUMN regionID CHAR(36), MODIFY COLUMN ProcMem INT(11) UNSIGNED NOT NULL, MODIFY COLUMN Prims INT(11) UNSIGNED NOT NULL, MODIFY COLUMN SimFPS FLOAT NOT NULL, MODIFY COLUMN PhyFPS FLOAT NOT NULL, MODIFY COLUMN OfflineTimer BIGINT UNSIGNED NOT NULL DEFAULT 0', + 'CREATE TRIGGER IF NOT EXISTS del_id_trig AFTER DELETE ON UserAccounts FOR EACH ROW DELETE FROM mcp_user_identities WHERE mcp_user_identities.PrincipalID = OLD.PrincipalID OR mcp_user_identities.IdentityID = OLD.PrincipalID', + 'CREATE TRIGGER IF NOT EXISTS del_pwres_trig AFTER DELETE ON UserAccounts FOR EACH ROW DELETE FROM mcp_password_reset WHERE mcp_password_reset.PrincipalID = OLD.PrincipalID' + ]/*, + 2 => [ + 'ALTER TRIGGER del_id_trig' + ]*/ + ]; + + private const MIGRATE_VERSION_CURRENT = 2; + + private int $migrateVersion; + private string $migrateVersionFile; + + public function __construct(string $migrateVersionFile) + { + $this->migrateVersionFile = $migrateVersionFile; + $this->migrateVersion = 0; + $apcu = false; + if ($this->apcuAvailable()) { + $this->migrateVersion = apcu_exists('mcp_migrate_version') ? apcu_fetch('mcp_migrate_version') : 0; + $apcu = true; + } + + if ($this->migrateVersion == 0 && file_exists($migrateVersionFile)) { + $migrateVer = file_get_contents($migrateVersionFile); + if (preg_match('/^\d+$/', $migrateVer)) { + $this->migrateVersion = intval($migrateVer); + if ($apcu) { + apcu_store('mcp_migrate_version', $this->migrateVersion); + } + } + } + } + + public function isMigrated(): bool + { + return $this->migrateVersion == $this::MIGRATE_VERSION_CURRENT; + } + + public function migrate(PDO $db): bool + { + if ($this->migrateVersion == 0) { + // MCP < v2.x.x might be initialized without migration data + $checkLegacy = $db->prepare('SHOW TABLES LIKE ?'); + $checkLegacy->execute(['UserIdentitys']); + if ($checkLegacy->rowCount() > 0) { + $this->migrateVersion = 1; + } + else { + return $this->runStatements($db, $this::INIT) && $this->updateVersion(); + } + } + + for ($ver = $this->migrateVersion; $ver <= count($this::MIGRATIONS); $ver++) { + if (!isset($this::MIGRATIONS[$ver])) { + break; + } + + if (!$this->runStatements($db, $this::MIGRATIONS[$ver])) { + return false; + } + } + + $this->updateVersion(); + return true; + } + + private function updateVersion(): bool + { + file_put_contents($this->migrateVersionFile, $this::MIGRATE_VERSION_CURRENT); + if ($this->apcuAvailable()) { + apcu_store('mcp_migrate_version', $this::MIGRATE_VERSION_CURRENT); + } + return true; + } + + private function runStatements(PDO $db, array $stmts): bool + { + try { + foreach ($stmts as $stmt) { + $db->exec($stmt); + } + return true; + } catch (Exception $e) { + error_log('Could not execute statements: '.$e->getMessage()."\n".$e->getTraceAsString()); + return false; + } + } + + private function apcuAvailable(): bool + { + return function_exists('apcu_fetch') && apcu_enabled(); + } +}