patch sql -> cli

This commit is contained in:
Roman 2021-04-06 20:59:55 +02:00
parent 186083a315
commit ebdece7144
4 changed files with 102 additions and 127 deletions

@ -37,7 +37,7 @@ The compiled dist files will be automatically moved to `/js`.
Each API endpoint has usually one overlying category, for example all user and authorization endpoints belong to the [UserAPI](/core/Api/UserAPI.class.php). Each API endpoint has usually one overlying category, for example all user and authorization endpoints belong to the [UserAPI](/core/Api/UserAPI.class.php).
These endpoints can be accessed by requesting URLs starting with `/api/user`, for example: `/api/user/login`. There are also endpoints, which don't have These endpoints can be accessed by requesting URLs starting with `/api/user`, for example: `/api/user/login`. There are also endpoints, which don't have
a category, e.g. [PatchSQL](/core/Api/PatchSQL.class.php). These functions can be called directly, for example with `/api/patchSQL`. Both methods have one thing in common: a category, e.g. [VerifyCaptcha](/core/Api/VerifyCaptcha.class.php). These functions can be called directly, for example with `/api/verifyCaptcha`. Both methods have one thing in common:
Each endpoint is represented by a class inheriting the [Request Class](/core/Api/Request.class.php). An example endpoint looks like this: Each endpoint is represented by a class inheriting the [Request Class](/core/Api/Request.class.php). An example endpoint looks like this:
```php ```php
@ -112,7 +112,7 @@ If any result is expected from the api call, the `$req->getResult()` method can
This step is not really required, as and changes made to the database must not be presented inside the code. This step is not really required, as and changes made to the database must not be presented inside the code.
On the other hand, it is recommended to keep track of any modifications for later use or to deploy the application On the other hand, it is recommended to keep track of any modifications for later use or to deploy the application
to other systems. Therefore, either the [default installation script](/core/Configuration/CreateDatabase.class.php) or to other systems. Therefore, either the [default installation script](/core/Configuration/CreateDatabase.class.php) or
an additional patch file, which can be executed using the API (`/api/PatchSQL`), can be created. The patch files are usually an additional patch file, which can be executed using the [CLI](/cli.php), can be created. The patch files are usually
located in [/core/Configuration/Patch](/core/Configuration/Patch) and have the following structure: located in [/core/Configuration/Patch](/core/Configuration/Patch) and have the following structure:
```php ```php

157
cli.php

@ -44,76 +44,117 @@ function printHelp() {
function handleDatabase($argv) { function handleDatabase($argv) {
$action = $argv[2] ?? ""; $action = $argv[2] ?? "";
switch ($action) { if ($action === "migrate") {
case 'migrate': $class = $argv[3] ?? null;
$class = $argv[3] ?? null; if (!$class) {
if (!$class) { die("Usage: cli.php db migrate <class name>\n");
die("Usage: cli.php db migrate <class name>\n"); }
$class = str_replace('/', '\\', $class);
$className = "\\Configuration\\$class";
$classPath = getClassPath($className);
if (!file_exists($classPath) || !is_readable($classPath)) {
die("Database script file does not exist or is not readable\n");
}
include_once $classPath;
$obj = new $className();
if (!($obj instanceof DatabaseScript)) {
die("Not a database script\n");
}
$db = connectDatabase();
$queries = $obj->createQueries($db);
foreach ($queries as $query) {
if (!$query->execute($db)) {
die($db->getLastError());
}
}
$db->close();
} else if ($action === "export" || $action === "import") {
// database config
$config = getDatabaseConfig();
$dbType = $config->getProperty("type") ?? null;
$user = $config->getLogin();
$password = $config->getPassword();
$database = $config->getProperty("database");
$host = $config->getHost();
$port = $config->getPort();
// subprocess config
$env = [];
$options = array_slice($argv, 3);
$dataOnly = in_array("--data-only", $options) || in_array("-d", $options);
$descriptorSpec = [STDIN, STDOUT, STDOUT];
$inputData = null;
// argument config
if ($action === "import") {
$file = $argv[3] ?? null;
if (!$file) {
die("Usage: cli.php db import <path>\n");
} }
$class = str_replace('/', '\\', $class); if (!file_exists($file) || !is_readable($file)) {
$className = "\\Configuration\\$class"; die("File not found or not readable\n");
$classPath = getClassPath($className);
if (!file_exists($classPath) || !is_readable($classPath)) {
die("Database script file does not exist or is not readable\n");
} }
include_once $classPath; $inputData = file_get_contents($file);
$obj = new $className(); }
if (!($obj instanceof DatabaseScript)) {
die("Not a database script\n");
}
$db = connectDatabase(); if ($dbType === "mysql") {
$queries = $obj->createQueries($db); $command_args = ["-u", $user, '-h', $host, '-P', $port, "--password=$password"];
foreach ($queries as $query) { if ($action === "export") {
if (!$query->execute($db)) { $command_bin = "mysqldump";
die($db->getLastError()); if ($dataOnly) {
$command_args[] = "--skip-triggers";
$command_args[] = "--compact";
$command_args[] = "--no-create-info";
} }
} } else if ($action === "import") {
$command_bin = "mysql";
$db->close(); $descriptorSpec[0] = ["pipe", "r"];
break;
case 'export':
$config = getDatabaseConfig();
$dbType = $config->getProperty("type") ?? null;
$user = $config->getLogin();
$password = $config->getPassword();
$database = $config->getProperty("database");
$host = $config->getHost();
$port = $config->getPort();
$env = [];
$output = $argv[3] ?? null;
$descriptorSpec = [STDIN, STDOUT, STDOUT];
if ($dbType === "mysql") {
$command = ["mysqldump", "-u", $user, '-h', $host, '-P', $port, "--password=$password"];
if ($database) {
$command[] = $database;
}
} else if ($dbType === "postgres") {
$command = ["pg_dump", "-U", $user, '-h', $host, '-p', $port];
if ($database) {
$command[] = $database;
}
$env["PGPASSWORD"] = $password;
} else { } else {
die("Unsupported database type\n"); die("Unsupported action\n");
}
} else if ($dbType === "postgres") {
$env["PGPASSWORD"] = $password;
$command_args = ["-U", $user, '-h', $host, '-p', $port];
if ($action === "export") {
$command_bin = "/usr/bin/pg_dump";
if ($dataOnly) {
$command_args[] = "--data-only";
}
} else if ($action === "import") {
$command_bin = "/usr/bin/psql";
$descriptorSpec[0] = ["pipe", "r"];
} else {
die("Unsupported action\n");
} }
if ($output) { } else {
$descriptorSpec[1] = ["file", $output, "w"]; die("Unsupported database type\n");
}
if ($database) {
$command_args[] = $database;
}
$command = array_merge([$command_bin], $command_args);
$process = proc_open($command, $descriptorSpec, $pipes, null, $env);
if (is_resource($process)) {
if ($action === "import" && $inputData && count($pipes) > 0) {
fwrite($pipes[0], $inputData);
fclose($pipes[0]);
} }
$process = proc_open($command, $descriptorSpec, $pipes, null, $env);
proc_close($process); proc_close($process);
break; }
default:
die("Usage: cli.php db <import|export|migrate>\n");
} }
} }

@ -1,65 +0,0 @@
<?php
namespace Api;
use Api\Parameter\StringType;
use Configuration\DatabaseScript;
use Objects\User;
class PatchSQL extends Request {
public function __construct(User $user, bool $externalCall = false) {
parent::__construct($user, $externalCall, array(
"className" => new StringType("className", 64)
));
$this->loginRequired = true;
$this->csrfTokenRequired = false;
}
public function execute($values = array()): bool {
if (!parent::execute($values)) {
return false;
}
$className = $this->getParam("className");
$fullClassName = "\\Configuration\\Patch\\" . $className;
$path = getClassPath($fullClassName, true);
if (!file_exists($path)) {
return $this->createError("File not found");
}
if(!class_exists($fullClassName)) {
return $this->createError("Class not found.");
}
try {
$reflection = new \ReflectionClass($fullClassName);
if (!$reflection->isInstantiable()) {
return $this->createError("Class is not instantiable");
}
if (!$reflection->isSubclassOf(DatabaseScript::class)) {
return $this->createError("Not a database script.");
}
$sql = $this->user->getSQL();
$obj = $reflection->newInstance();
$queries = $obj->createQueries($sql);
if (!is_array($queries)) {
return $this->createError("Database script returned invalid values");
}
foreach($queries as $query) {
if (!$query->execute()) {
return $this->createError("Query error: " . $sql->getLastError());
}
}
$this->success = true;
} catch (\ReflectionException $e) {
return $this->createError("Error reflecting class: " . $e->getMessage());
}
return $this->success;
}
}

@ -193,8 +193,7 @@ class CreateDatabase extends DatabaseScript {
->addRow("User/edit", array(USER_GROUP_ADMIN), "Allows users to edit details and group memberships of any user") ->addRow("User/edit", array(USER_GROUP_ADMIN), "Allows users to edit details and group memberships of any user")
->addRow("User/delete", array(USER_GROUP_ADMIN), "Allows users to delete any other user") ->addRow("User/delete", array(USER_GROUP_ADMIN), "Allows users to delete any other user")
->addRow("Permission/fetch", array(USER_GROUP_ADMIN), "Allows users to list all API permissions") ->addRow("Permission/fetch", array(USER_GROUP_ADMIN), "Allows users to list all API permissions")
->addRow("Visitors/stats", array(USER_GROUP_ADMIN, USER_GROUP_SUPPORT), "Allows users to see visitor statistics") ->addRow("Visitors/stats", array(USER_GROUP_ADMIN, USER_GROUP_SUPPORT), "Allows users to see visitor statistics");
->addRow("PatchSQL", array(USER_GROUP_ADMIN), "Allows users to import database patches");
self::loadPatches($queries, $sql); self::loadPatches($queries, $sql);