diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/application/mystic/forum/Database.php | 26 | ||||
-rw-r--r-- | src/application/mystic/forum/Messaging.php | 101 | ||||
-rw-r--r-- | src/application/mystic/forum/attributes/References.php | 5 | ||||
-rw-r--r-- | src/application/mystic/forum/exceptions/DatabaseConnectionException.php | 6 | ||||
-rw-r--r-- | src/application/mystic/forum/orm/Post.php | 2 | ||||
-rw-r--r-- | src/application/mystic/forum/utils/RequestUtils.php | 19 | ||||
-rw-r--r-- | src/index.php | 61 |
7 files changed, 216 insertions, 4 deletions
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"); +} |