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).
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:
```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.
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
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:
```php

79
cli.php

@ -44,8 +44,7 @@ function printHelp() {
function handleDatabase($argv) {
$action = $argv[2] ?? "";
switch ($action) {
case 'migrate':
if ($action === "migrate") {
$class = $argv[3] ?? null;
if (!$class) {
die("Usage: cli.php db migrate <class name>\n");
@ -73,8 +72,9 @@ function handleDatabase($argv) {
}
$db->close();
break;
case 'export':
} else if ($action === "export" || $action === "import") {
// database config
$config = getDatabaseConfig();
$dbType = $config->getProperty("type") ?? null;
$user = $config->getLogin();
@ -83,37 +83,78 @@ function handleDatabase($argv) {
$host = $config->getHost();
$port = $config->getPort();
// subprocess config
$env = [];
$output = $argv[3] ?? null;
$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");
}
if (!file_exists($file) || !is_readable($file)) {
die("File not found or not readable\n");
}
$inputData = file_get_contents($file);
}
if ($dbType === "mysql") {
$command = ["mysqldump", "-u", $user, '-h', $host, '-P', $port, "--password=$password"];
if ($database) {
$command[] = $database;
$command_args = ["-u", $user, '-h', $host, '-P', $port, "--password=$password"];
if ($action === "export") {
$command_bin = "mysqldump";
if ($dataOnly) {
$command_args[] = "--skip-triggers";
$command_args[] = "--compact";
$command_args[] = "--no-create-info";
}
} else if ($action === "import") {
$command_bin = "mysql";
$descriptorSpec[0] = ["pipe", "r"];
} else {
die("Unsupported action\n");
}
} else if ($dbType === "postgres") {
$command = ["pg_dump", "-U", $user, '-h', $host, '-p', $port];
if ($database) {
$command[] = $database;
}
$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");
}
} else {
die("Unsupported database type\n");
}
if ($output) {
$descriptorSpec[1] = ["file", $output, "w"];
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]);
}
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/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("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");
->addRow("Visitors/stats", array(USER_GROUP_ADMIN, USER_GROUP_SUPPORT), "Allows users to see visitor statistics");
self::loadPatches($queries, $sql);