From 34b1b391d4b03659a96f868857c230002b351514 Mon Sep 17 00:00:00 2001
From: Jonas Kohl
Date: Mon, 9 Sep 2024 17:57:00 +0200
Subject: More updates

---
 src/application/mystic/forum/Database.php          |  26 +++++-
 src/application/mystic/forum/Messaging.php         | 101 +++++++++++++++++++++
 .../mystic/forum/attributes/References.php         |   5 +-
 .../exceptions/DatabaseConnectionException.php     |   6 ++
 src/application/mystic/forum/orm/Post.php          |   2 +-
 .../mystic/forum/utils/RequestUtils.php            |  19 ++++
 src/index.php                                      |  61 ++++++++++++-
 7 files changed, 216 insertions(+), 4 deletions(-)
 create mode 100644 src/application/mystic/forum/Messaging.php
 create mode 100644 src/application/mystic/forum/exceptions/DatabaseConnectionException.php
 create mode 100644 src/application/mystic/forum/utils/RequestUtils.php

(limited to 'src')

diff --git a/src/application/mystic/forum/Database.php b/src/application/mystic/forum/Database.php
index f574386..9b9cf55 100644
--- a/src/application/mystic/forum/Database.php
+++ b/src/application/mystic/forum/Database.php
@@ -10,6 +10,7 @@ use mystic\forum\attributes\NotNull;
 use mystic\forum\attributes\PrimaryKey;
 use mystic\forum\attributes\References;
 use mystic\forum\attributes\Table;
+use mystic\forum\exceptions\DatabaseConnectionException;
 use mystic\forum\orm\Entity;
 use mystic\forum\utils\ArrayUtils;
 use mystic\forum\utils\StringUtils;
@@ -26,7 +27,13 @@ class Database {
     private const REFERENCES  = 0b0000_0100;
 
     public function __construct(string $connectionString) {
-        $this->connection = \pg_connect($connectionString);
+        try {
+            $conn = \pg_connect($connectionString);
+            if ($conn !== false)
+                $this->connection = $conn;
+        } catch (\ErrorException $ex) {
+            throw new DatabaseConnectionException($ex->getMessage(), $ex->getCode(), $ex);
+        }
     }
 
     public static function getConnectionString(string $host, string $user, string $password, string $dbname, int $port = 5432): string {
@@ -281,6 +288,23 @@ class Database {
         return true;
     }
 
+    public function delete(Entity &$entity): bool {
+        $entityClassName = get_class($entity);
+        $tableName = self::getTableName($entityClassName);
+        $reflClass = new ReflectionClass($entityClassName);
+        $cols = self::getColumns($reflClass);
+        $primaryCol = self::getPrimaryKeyColumn($cols);
+        if ($primaryCol === null)
+            throw new \RuntimeException("Deleting an entity requires a primary key column to be specified");
+        $query = "DELETE FROM $tableName WHERE $primaryCol = \$1;";
+        $result = \pg_query_params($this->connection, $query, [ $entity->{$cols[$primaryCol]["propertyName"]} ]);
+        if ($result === false)
+            throw new \RuntimeException("Deletion failed: " . \pg_last_error($this->connection));
+        $num_affected_rows = \pg_affected_rows($result);
+        \pg_free_result($result);
+        return $num_affected_rows >= 1;
+    }
+
     public function update(Entity &$entity): bool {
         $tableName = self::getTableName(get_class($entity));
         $reflClass = new ReflectionClass($entity);
diff --git a/src/application/mystic/forum/Messaging.php b/src/application/mystic/forum/Messaging.php
new file mode 100644
index 0000000..2f5c80d
--- /dev/null
+++ b/src/application/mystic/forum/Messaging.php
@@ -0,0 +1,101 @@
+<?php
+declare(strict_types=1);
+
+namespace mystic\forum;
+
+use mystic\forum\utils\StaticClass;
+
+final class Messaging {
+    use StaticClass;
+
+    const ENT_FLAGS = \ENT_COMPAT | \ENT_HTML401 | \ENT_SUBSTITUTE;
+
+    protected static function message(array $items, string $headerText, string $headerColor, string $headerTextColor): void {
+        echo "<HTML><BODY>\n";
+        echo "<TABLE cellspacing=2 cellpadding=0 bgcolor=gray><TR><TD>\n";
+
+        echo "<TABLE width=\"100%\" cellspacing=0 cellpadding=4 bgcolor=\"".htmlentities($headerColor, self::ENT_FLAGS)."\"><TR><TD align=center>\n";
+        echo "<FONT face=Arial size=3 color=\"".htmlentities($headerTextColor, self::ENT_FLAGS)."\"><B>".htmlentities($headerText, self::ENT_FLAGS)."</B></FONT>\n";
+        echo "</TD></TR></TABLE>\n";
+        
+        echo "</TD></TR><TR><TD>\n";
+
+        echo "<TABLE cellspacing=0 cellpadding=4 bgcolor=WHITE><TR><TD align=left><FONT size=2 face=Arial color=BLACK>\n";
+        
+        foreach ($items as $item) {
+            if (is_scalar($item)) {
+                echo "<P>" . htmlentities(strval($item), self::ENT_FLAGS) . "</P>\n";
+            } elseif (is_array($item)) {
+                if (count(array_keys($item)) === 2 && isset($item["___!type"]) && isset($item["___!content"])) {
+                    // special item
+                    switch ($item["___!type"]) {
+                        case "HTML":
+                            echo $item["___!content"];
+                            break;
+                        default:
+                            echo "invalid";
+                            break;
+                    }
+                } elseif (array_is_list($item)) {
+                    echo "<UL>\n";
+                    foreach ($item as $i)
+                        echo "<LI>" . htmlentities($i, self::ENT_FLAGS) . "</LI>\n";
+                    echo "</UL>\n";
+                } else {
+                    echo "<TABLE cellspacing=0 cellpadding=2 border=1>\n";
+                    foreach ($item as $k => $i) {
+                        echo "<TR>\n";
+                        echo "<TH align=left>" . htmlentities($k, self::ENT_FLAGS) . "</TH>\n";
+                        echo "<TD>";
+                        if (is_scalar($i)) {
+                            echo htmlentities($i, self::ENT_FLAGS);
+                        } else {
+                            echo gettype($i);
+                        }
+                        echo "</TD>";
+                        echo "</TR>\n";
+                    }
+                    echo "</TABLE>\n";
+                }
+            } else {
+                echo gettype($item);
+            }
+        }
+
+        echo "</FONT></TD></TR></TABLE>\n";
+
+        echo "</TD></TR></TABLE>\n";
+        echo "</BODY></HTML>\n";
+    }
+
+    public static function bold(string $contents): array {
+        return self::html("<P><B>" . htmlentities($contents, self::ENT_FLAGS) . "</B></P>\n");
+    }
+
+    public static function italic(string $contents): array {
+        return self::html("<P><I>" . htmlentities($contents, self::ENT_FLAGS) . "</I></P>\n");
+    }
+
+    public static function bold_italic(string $contents): array {
+        return self::html("<P><B><I>" . htmlentities($contents, self::ENT_FLAGS) . "</I></B></P>\n");
+    }
+
+    public static function html(string $contents): array {
+        return [
+            "___!type" => "HTML",
+            "___!content" => $contents,
+        ];
+    }
+
+    public static function info(string|array $contents): void {
+        if (is_string($contents))
+            $contents = [$contents];
+        self::message($contents, "INFORMATION", "SKYBLUE", "BLACK");
+    }
+
+    public static function error(string|array $contents): void {
+        if (is_string($contents))
+            $contents = [$contents];
+        self::message($contents, "ERROR", "RED", "WHITE");
+    }
+}
diff --git a/src/application/mystic/forum/attributes/References.php b/src/application/mystic/forum/attributes/References.php
index ac431ff..9e33927 100644
--- a/src/application/mystic/forum/attributes/References.php
+++ b/src/application/mystic/forum/attributes/References.php
@@ -7,9 +7,12 @@ class References {
     public function __construct(
         public readonly string $foreignTableName,
         public readonly ?string $foreignColumnName = null,
+        public readonly bool $cascadeOnDelete = false,
     ) {}
 
     public function __toString(): string {
-        return $this->foreignTableName . ($this->foreignColumnName !== null ? " ({$this->foreignColumnName})" : "");
+        return $this->foreignTableName
+            . ($this->foreignColumnName !== null ? " ({$this->foreignColumnName})" : "")
+            . ($this->cascadeOnDelete ? " ON DELETE CASCADE" : "");
     }
 }
diff --git a/src/application/mystic/forum/exceptions/DatabaseConnectionException.php b/src/application/mystic/forum/exceptions/DatabaseConnectionException.php
new file mode 100644
index 0000000..f79d800
--- /dev/null
+++ b/src/application/mystic/forum/exceptions/DatabaseConnectionException.php
@@ -0,0 +1,6 @@
+<?php
+declare(strict_types=1);
+
+namespace mystic\forum\exceptions;
+
+class DatabaseConnectionException extends \Exception {}
diff --git a/src/application/mystic/forum/orm/Post.php b/src/application/mystic/forum/orm/Post.php
index db297e5..2c9a38c 100644
--- a/src/application/mystic/forum/orm/Post.php
+++ b/src/application/mystic/forum/orm/Post.php
@@ -13,5 +13,5 @@ class Post extends Entity {
     public string $content;
     #[References("public.users")] public string $authorId;
     public \DateTimeImmutable $postDate;
-    #[References("public.topics")] public string $topicId;
+    #[References("public.topics", cascadeOnDelete: true)] public string $topicId;
 }
diff --git a/src/application/mystic/forum/utils/RequestUtils.php b/src/application/mystic/forum/utils/RequestUtils.php
new file mode 100644
index 0000000..2f40013
--- /dev/null
+++ b/src/application/mystic/forum/utils/RequestUtils.php
@@ -0,0 +1,19 @@
+<?php
+declare(strict_types=1);
+
+namespace mystic\forum\utils;
+
+use mystic\forum\Messaging;
+
+final class RequestUtils {
+    use StaticClass;
+
+    public static function ensureRequestMethod(string $method): void {
+        $rMethod = $_SERVER["REQUEST_METHOD"];
+        if (strcasecmp($rMethod, $method) !== 0) {
+            http_response_code(500);
+            Messaging::error("Invalid request method $rMethod");
+            exit;
+        }
+    }
+}
diff --git a/src/index.php b/src/index.php
index 62a300a..8e2ce4e 100644
--- a/src/index.php
+++ b/src/index.php
@@ -1,13 +1,72 @@
 <?php
 
 use mystic\forum\Database;
+use mystic\forum\exceptions\DatabaseConnectionException;
+use mystic\forum\Messaging;
 use mystic\forum\orm\Post;
 use mystic\forum\orm\Topic;
 use mystic\forum\orm\User;
+use mystic\forum\utils\RequestUtils;
+
+function exception_error_handler($errno, $errstr, $errfile, $errline ) {
+    throw new ErrorException(html_entity_decode($errstr), $errno, 0, $errfile, $errline);
+}
+set_error_handler("exception_error_handler");
+
+session_name("fsid");
+session_start();
+
+$_rq_method = $_SERVER["REQUEST_METHOD"] ?? "GET";
+$_action = $_GET["_action"] ?? null;
 
 require_once __DIR__ . "/vendor/autoload.php";
 
-$db = new Database(Database::getConnectionString("db", "postgres", "postgres", "postgres"));
+$db = null;
+try {
+    $db = new Database(Database::getConnectionString("db", "postgres", "postgres", "postgres"));
+} catch (DatabaseConnectionException $ex) {
+    Messaging::error([
+        Messaging::bold("Failed to connect to database!"),
+        Messaging::italic($ex->getMessage()),
+    ]);
+    exit;
+}
+
 $db->ensureTable(User::class);
 $db->ensureTable(Topic::class);
 $db->ensureTable(Post::class);
+
+$superuser = new User();
+$superuser->id = "SUPERUSER";
+if (!$db->fetch($superuser)) {
+    $superUserPassword = base64_encode(random_bytes(12));
+
+    $superuser->name = "superuser";
+    $superuser->passwordHash = password_hash($superUserPassword, PASSWORD_DEFAULT);
+    $superuser->displayName = "SuperUser";
+    $superuser->created = new \DateTimeImmutable();
+
+    $db->insert($superuser);
+
+    Messaging::info([
+        Messaging::bold("Superuser account created"),
+        [
+            "Username" => $superuser->name,
+            "Password" => $superUserPassword,
+        ],
+        "Please note that the password can only be shown this time, so please note it down!",
+    ]);
+    exit;
+}
+
+// initialization finished
+
+if ($_action === "auth") {
+    RequestUtils::ensureRequestMethod("POST");
+    // TODO Login logic
+} elseif ($_action === null) {
+    echo "Hello";
+} else {
+    http_response_code(404);
+    Messaging::error("Invalid or unknown action $_action");
+}
-- 
cgit v1.2.3