Browse Source

patch sql -> cli

Roman 3 years ago
parent
commit
ebdece7144
4 changed files with 97 additions and 122 deletions
  1. 2 2
      README.md
  2. 94 53
      cli.php
  3. 0 65
      core/Api/PatchSQL.class.php
  4. 1 2
      core/Configuration/CreateDatabase.class.php

+ 2 - 2
README.md

@@ -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

+ 94 - 53
cli.php

@@ -44,76 +44,117 @@ function printHelp() {
 function handleDatabase($argv) {
   $action = $argv[2] ?? "";
 
-  switch ($action) {
-    case 'migrate':
-      $class = $argv[3] ?? null;
-      if (!$class) {
-        die("Usage: cli.php db migrate <class name>\n");
+  if ($action === "migrate") {
+    $class = $argv[3] ?? null;
+    if (!$class) {
+      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());
       }
+    }
 
-      $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");
+    $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");
       }
 
-      include_once $classPath;
-      $obj = new $className();
-      if (!($obj instanceof DatabaseScript)) {
-        die("Not a database script\n");
+      if (!file_exists($file) || !is_readable($file)) {
+        die("File not found or not readable\n");
       }
 
-      $db = connectDatabase();
-      $queries = $obj->createQueries($db);
-      foreach ($queries as $query) {
-        if (!$query->execute($db)) {
-          die($db->getLastError());
+      $inputData = file_get_contents($file);
+    }
+
+    if ($dbType === "mysql") {
+      $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") {
 
-      $db->close();
-      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;
-        }
+      $env["PGPASSWORD"] = $password;
+      $command_args = ["-U", $user, '-h', $host, '-p', $port];
 
-      } else if ($dbType === "postgres") {
-        $command = ["pg_dump", "-U", $user, '-h', $host, '-p', $port];
-        if ($database) {
-          $command[] = $database;
+      if ($action === "export") {
+        $command_bin = "/usr/bin/pg_dump";
+        if ($dataOnly) {
+          $command_args[] = "--data-only";
         }
-
-        $env["PGPASSWORD"] = $password;
+      } else if ($action === "import") {
+        $command_bin = "/usr/bin/psql";
+        $descriptorSpec[0] = ["pipe", "r"];
       } else {
-        die("Unsupported database type\n");
+        die("Unsupported action\n");
       }
 
-      if ($output) {
-        $descriptorSpec[1] = ["file", $output, "w"];
+    } else {
+      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);
-      break;
-    default:
-      die("Usage: cli.php db <import|export|migrate>\n");
+    }
   }
 }
 

+ 0 - 65
core/Api/PatchSQL.class.php

@@ -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;
-  }
-}

+ 1 - 2
core/Configuration/CreateDatabase.class.php

@@ -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);