aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--WpfTest/Db.php41
-rw-r--r--WpfTest/Db/Migrator.php52
-rw-r--r--WpfTest/GlobalConfig.php21
-rw-r--r--WpfTest/HttpResponseStatus.php (renamed from WpfTest/HttpReturnStatus.php)2
-rw-r--r--WpfTest/HttpResponseStatus/BadRequest.php (renamed from WpfTest/HttpReturnStatus/BadRequest.php)6
-rw-r--r--WpfTest/HttpResponseStatus/InternalServerError.php23
-rw-r--r--WpfTest/HttpResponseStatus/NoHandlerFound.php (renamed from WpfTest/HttpReturnStatus/NoHandlerFound.php)6
-rw-r--r--WpfTest/HttpResponseStatus/NotFound.php (renamed from WpfTest/HttpReturnStatus/NotFound.php)6
-rw-r--r--WpfTest/HttpReturnStatus/InternalServerError.php16
-rw-r--r--WpfTest/Model.php73
-rw-r--r--WpfTest/Model/Actor.php26
-rw-r--r--WpfTest/Model/ActorEndpoints.php15
-rw-r--r--WpfTest/Router.php29
-rw-r--r--WpfTest/Singleton.php18
-rwxr-xr-xbin/migrate10
-rwxr-xr-xbin/newmigration22
-rw-r--r--composer.json11
-rw-r--r--composer.lock136
-rw-r--r--index.php11
-rw-r--r--migrations/20241207_183000_create_actor.php40
-rw-r--r--routes/actor.php32
-rw-r--r--routes/nodeinfo.php10
-rw-r--r--routes/webfinger.php4
-rw-r--r--schema.sql9
25 files changed, 537 insertions, 83 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a725465
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+vendor/ \ No newline at end of file
diff --git a/WpfTest/Db.php b/WpfTest/Db.php
new file mode 100644
index 0000000..3de1011
--- /dev/null
+++ b/WpfTest/Db.php
@@ -0,0 +1,41 @@
+<?php
+namespace WpfTest;
+
+use WpfTest\Model\Actor;
+
+class Db extends Singleton {
+ private const INIT_SCHEMA_PATH = __DIR__ . '/../schema.sql';
+
+ private \PDO $conn;
+
+ protected function __construct() {
+ $this->conn = new \PDO('pgsql:host=localhost;dbname=wpftest', 'wpftest', 'wpftest', [
+ \PDO::ATTR_PERSISTENT => true,
+ ]);
+ }
+
+ public function runInitialSchema() {
+ $this->conn->exec(file_get_contents(self::INIT_SCHEMA_PATH));
+ }
+
+ public function getDbVersion(): int {
+ $result = $this->conn->query('SELECT db_version FROM db_version');
+ return $result->fetch()['db_version'];
+ }
+
+ /**
+ * Changes the database schema version
+ *
+ * CAREFUL! this shouldn't be used unless you absolutely know why
+ * @param int $newVersion the new version number
+ * @return void
+ */
+ public function setDbVersion(int $newVersion) {
+ $stmt = $this->conn->prepare('UPDATE db_version SET db_version = ?');
+ $stmt->execute([$newVersion]);
+ }
+
+ public function getPdo(): \PDO {
+ return $this->conn;
+ }
+} \ No newline at end of file
diff --git a/WpfTest/Db/Migrator.php b/WpfTest/Db/Migrator.php
new file mode 100644
index 0000000..ea7f92a
--- /dev/null
+++ b/WpfTest/Db/Migrator.php
@@ -0,0 +1,52 @@
+<?php
+namespace WpfTest\Db;
+
+use WpfTest\Db;
+use WpfTest\Singleton;
+
+class Migrator extends Singleton {
+ private array $migrations = [];
+
+ public function register(int $newVersion, callable $fn) {
+ $this->migrations[$newVersion] = $fn;
+ }
+
+ /**
+ * Runs all available migrations
+ * @return void
+ */
+ public function migrate() {
+ $db = Db::getInstance();
+ $currentVersion = null;
+ try {
+ $currentVersion = $db->getDbVersion();
+ error_log('starting on version ' . $currentVersion);
+ } catch (\PDOException $e) {
+ if ($e->getCode() == '42P01') { // undefined table
+ error_log("initialising database because db version table doesn't exist");
+ $db->runInitialSchema();
+ $currentVersion = $db->getDbVersion();
+ error_log('starting on version ' . $currentVersion);
+ } else
+ throw $e;
+ }
+
+ // find migrations that haven't been run yet
+ $available = array_filter($this->migrations, function (int $key) use ($currentVersion) {
+ return $key > $currentVersion;
+ }, ARRAY_FILTER_USE_KEY);
+
+ try {
+ // run them in order (at least they should be in order?)
+ foreach (array_keys($available) as $version) {
+ $fn = $available[$version];
+ $filename = basename((new \ReflectionFunction($fn))->getFileName());
+ error_log("running migration $filename");
+ $available[$version]($db->getPdo());
+ $db->setDbVersion($version);
+ }
+ } finally {
+ error_log('now on version ' . $db->getDbVersion());
+ }
+ }
+} \ No newline at end of file
diff --git a/WpfTest/GlobalConfig.php b/WpfTest/GlobalConfig.php
index 4bbfe68..03cfe6f 100644
--- a/WpfTest/GlobalConfig.php
+++ b/WpfTest/GlobalConfig.php
@@ -1,25 +1,16 @@
<?php
namespace WpfTest;
-class GlobalConfig {
- private static GlobalConfig $instance;
-
- private function __construct() {
-
- }
-
- public static function getInstance() {
- if (!isset(self::$instance)) {
- self::$instance = new self();
- }
- return self::$instance;
- }
-
+class GlobalConfig extends Singleton {
public function getCanonicalHost(): string {
return $_SERVER['HTTP_HOST']; // for now this is fine
}
public function getBasePath(): string {
- return 'http://' . $this->getCanonicalHost(); // fix this?
+ return 'http://' . $this->getCanonicalHost(); // FIXME: don't hardcode http
+ }
+
+ public function shouldReportTraces(): bool {
+ return true; // FIXME: don't hardcode
}
} \ No newline at end of file
diff --git a/WpfTest/HttpReturnStatus.php b/WpfTest/HttpResponseStatus.php
index 60622bb..c81e000 100644
--- a/WpfTest/HttpReturnStatus.php
+++ b/WpfTest/HttpResponseStatus.php
@@ -1,7 +1,7 @@
<?php
namespace WpfTest;
-interface HttpReturnStatus extends \Throwable {
+interface HttpResponseStatus extends \Throwable {
public function httpCode(): int;
public function httpReason(): string;
} \ No newline at end of file
diff --git a/WpfTest/HttpReturnStatus/BadRequest.php b/WpfTest/HttpResponseStatus/BadRequest.php
index 83b5011..74fab3c 100644
--- a/WpfTest/HttpReturnStatus/BadRequest.php
+++ b/WpfTest/HttpResponseStatus/BadRequest.php
@@ -1,9 +1,9 @@
<?php
-namespace WpfTest\HttpReturnStatus;
+namespace WpfTest\HttpResponseStatus;
-use WpfTest\HttpReturnStatus;
+use WpfTest\HttpResponseStatus;
-class BadRequest extends \Exception implements HttpReturnStatus {
+class BadRequest extends \Exception implements HttpResponseStatus {
public function __construct(?string $reason) {
$this->message = $reason ?? 'Request does not conform to expectations';
}
diff --git a/WpfTest/HttpResponseStatus/InternalServerError.php b/WpfTest/HttpResponseStatus/InternalServerError.php
new file mode 100644
index 0000000..11b67f3
--- /dev/null
+++ b/WpfTest/HttpResponseStatus/InternalServerError.php
@@ -0,0 +1,23 @@
+<?php
+namespace WpfTest\HttpResponseStatus;
+
+use WpfTest\GlobalConfig;
+use WpfTest\HttpResponseStatus;
+
+class InternalServerError extends \Exception implements HttpResponseStatus {
+ public function __construct(\Throwable $reason, ?string $context = null) {
+ $this->message = $reason::class . ': ' . $reason->getMessage();
+ if (isset($context)) {
+ $this->message .= " [$context]";
+ }
+ if (GlobalConfig::getInstance()->shouldReportTraces()) {
+ $this->message .= "\n" . $reason->getTraceAsString();
+ }
+ }
+ public function httpCode(): int {
+ return 500;
+ }
+ public function httpReason(): string {
+ return "Internal Server Error";
+ }
+} \ No newline at end of file
diff --git a/WpfTest/HttpReturnStatus/NoHandlerFound.php b/WpfTest/HttpResponseStatus/NoHandlerFound.php
index d914ca2..9ceeb0b 100644
--- a/WpfTest/HttpReturnStatus/NoHandlerFound.php
+++ b/WpfTest/HttpResponseStatus/NoHandlerFound.php
@@ -1,9 +1,9 @@
<?php
-namespace WpfTest\HttpReturnStatus;
+namespace WpfTest\HttpResponseStatus;
-use WpfTest\HttpReturnStatus;
+use WpfTest\HttpResponseStatus;
-class NoHandlerFound extends \Exception implements HttpReturnStatus {
+class NoHandlerFound extends \Exception implements HttpResponseStatus {
public function __construct(string $path) {
$this->message = "No handler found for path $path";
}
diff --git a/WpfTest/HttpReturnStatus/NotFound.php b/WpfTest/HttpResponseStatus/NotFound.php
index eef23b3..b36d0a3 100644
--- a/WpfTest/HttpReturnStatus/NotFound.php
+++ b/WpfTest/HttpResponseStatus/NotFound.php
@@ -1,9 +1,9 @@
<?php
-namespace WpfTest\HttpReturnStatus;
+namespace WpfTest\HttpResponseStatus;
-use WpfTest\HttpReturnStatus;
+use WpfTest\HttpResponseStatus;
-class NotFound extends \Exception implements HttpReturnStatus {
+class NotFound extends \Exception implements HttpResponseStatus {
public function __construct(?string $reason) {
$this->message = $reason ?? 'Request was handled but no appropriate resource found';
}
diff --git a/WpfTest/HttpReturnStatus/InternalServerError.php b/WpfTest/HttpReturnStatus/InternalServerError.php
deleted file mode 100644
index 0141e26..0000000
--- a/WpfTest/HttpReturnStatus/InternalServerError.php
+++ /dev/null
@@ -1,16 +0,0 @@
-<?php
-namespace WpfTest\HttpReturnStatus;
-
-use WpfTest\HttpReturnStatus;
-
-class InternalServerError extends \Exception implements HttpReturnStatus {
- public function __construct(\Exception $reason) {
- $this->message = $reason->getMessage();
- }
- public function httpCode(): int {
- return 500;
- }
- public function httpReason(): string {
- return "Internal Server Error";
- }
-} \ No newline at end of file
diff --git a/WpfTest/Model.php b/WpfTest/Model.php
new file mode 100644
index 0000000..26a7b49
--- /dev/null
+++ b/WpfTest/Model.php
@@ -0,0 +1,73 @@
+<?php
+namespace WpfTest;
+
+abstract class Model {
+ private static function mangleName(string $name): string {
+ if ($name == 'self')
+ return 'uri';
+ $chars = str_split($name);
+ $chars[0] = strtolower($chars[0]);
+ for ($i = 0; $i < count($chars); $i++) {
+ $c = $chars[$i];
+ if (ctype_upper($c)) {
+ array_splice($chars, $i, 1, ['_', strtolower($c)]);
+ }
+ }
+ return implode($chars);
+ }
+
+ private static function unmangleName(string $name): string {
+ if ($name == 'uri')
+ return 'self';
+ $chars = str_split($name);
+ for ($i = 0; $i < count($chars); $i++) {
+ if ($chars[$i] == '_') {
+ array_splice($chars, $i, 2, [strtoupper($chars[$i + 1])]);
+ }
+ }
+ return implode($chars);
+ }
+
+ public function toDbRow(): array {
+ $refl = new \ReflectionObject($this);
+ $props = $refl->getProperties();
+ $result = [];
+ foreach ($props as $p) {
+ $result[self::mangleName($p->getName())] = $p->getValue();
+ }
+ return $result;
+ }
+
+ public static function fromDbRow(array|false $columns): ?static {
+ if ($columns === false)
+ return null;
+ $obj = new static();
+ $refl = new \ReflectionObject($obj);
+ foreach (array_keys($columns) as $name) {
+ $value = $columns[$name];
+ $name = self::unmangleName($name);
+ try {
+ $obj->{$name} = $value;
+ } catch (\TypeError $e) {
+ $type = $refl->getProperty($name)->getType();
+ if ($type == null || !is_a($type, \ReflectionNamedType::class))
+ throw $e;
+ $obj->{$name} = new ($type->getName())($value);
+ }
+ }
+ return $obj;
+ }
+
+ public static function findWhere(string $whereClause, array $parameters): ?static {
+ $classNameParts = explode('\\', static::class);
+ $className = $classNameParts[count($classNameParts) - 1];
+ $tableName = self::mangleName($className);
+ $stmt = Db::getInstance()->getPdo()->prepare("SELECT * FROM $tableName WHERE $whereClause");
+ $stmt->execute($parameters);
+ return static::fromDbRow($stmt->fetch(\PDO::FETCH_ASSOC));
+ }
+
+ public static function find(int $id): ?static {
+ return static::findWhere('id = ?', [$id]);
+ }
+} \ No newline at end of file
diff --git a/WpfTest/Model/Actor.php b/WpfTest/Model/Actor.php
new file mode 100644
index 0000000..cf8d7ca
--- /dev/null
+++ b/WpfTest/Model/Actor.php
@@ -0,0 +1,26 @@
+<?php
+namespace WpfTest\Model;
+
+use WpfTest\Model;
+
+class Actor extends Model {
+ public int $id;
+ public string $self;
+ public bool $isLocal = false;
+ public \DateTimeImmutable $created;
+ public ?\DateTimeImmutable $modified;
+ public ?string $homepage;
+ public string $handle;
+ public string $displayName;
+ public ?string $avatar;
+ public ?string $bio;
+ public ?string $pronouns;
+ public bool $automated = false;
+ public bool $requestToFollow = false;
+ public ActorEndpoints $endpoints;
+ public array $extensions = [];
+
+ public static function findLocalByHandle(string $handle): ?self {
+ return self::findWhere('is_local = true AND handle = ?', [$handle]);
+ }
+} \ No newline at end of file
diff --git a/WpfTest/Model/ActorEndpoints.php b/WpfTest/Model/ActorEndpoints.php
new file mode 100644
index 0000000..03dab9e
--- /dev/null
+++ b/WpfTest/Model/ActorEndpoints.php
@@ -0,0 +1,15 @@
+<?php
+namespace WpfTest\Model;
+
+use WpfTest\Model;
+
+class ActorEndpoints extends Model {
+ public string $basicFeed;
+ public ?string $fullFeed;
+ public ?string $follow;
+ public ?string $unfollow;
+ public ?string $block;
+ public ?string $unblock;
+ public ?string $subscribe;
+ public ?string $unsubscribe;
+} \ No newline at end of file
diff --git a/WpfTest/Router.php b/WpfTest/Router.php
index ea71657..c975723 100644
--- a/WpfTest/Router.php
+++ b/WpfTest/Router.php
@@ -1,51 +1,48 @@
<?php
namespace WpfTest;
-use WpfTest\HttpReturnStatus\InternalServerError;
-use WpfTest\HttpReturnStatus\NoHandlerFound;
+use WpfTest\HttpResponseStatus\InternalServerError;
+use WpfTest\HttpResponseStatus\NoHandlerFound;
-class Router {
- private static Router $instance;
+class Router extends Singleton {
private readonly string $requestPath;
private array $routes;
- private function __construct() {
+ protected function __construct() {
$this->requestPath = $_GET['requestPath'] ?? $_SERVER['PATH_INFO'] ?? explode('?', $_SERVER['REQUEST_URI'], 2)[0] ?? '/';
$this->routes = [];
}
/**
- * @return Router the singleton instance
+ * Processes and sends a response to the current request
+ * @return void
*/
- public static function getInstance() {
- if (!isset(self::$instance)) {
- self::$instance = new self();
- }
- return self::$instance;
- }
-
public function processRequest() {
try {
$this->processRequestInner();
- } catch (HttpReturnStatus $e) {
+ } catch (HttpResponseStatus $e) {
http_response_code($e->httpCode());
echo '<h1>' . $e->httpReason() . '</h1><pre>' . $e->getMessage() . '</pre>';
}
}
private function processRequestInner() {
+ $context = null;
try {
$route = $this->matchPath($this->requestPath);
if ($route == null) {
throw new NoHandlerFound($this->requestPath);
}
+ $context = 'handler for /' . implode('/', $route[0]);
$route[1]($route[2]); // call handler with args
} catch (\Exception $e) {
- if (is_a($e, HttpReturnStatus::class)) {
+ if (is_a($e, HttpResponseStatus::class)) {
throw $e;
}
- throw new InternalServerError($e);
+ throw new InternalServerError($e, $context);
+ } catch (\Error $e) {
+ throw new InternalServerError($e, $context);
}
}
diff --git a/WpfTest/Singleton.php b/WpfTest/Singleton.php
new file mode 100644
index 0000000..b365e1e
--- /dev/null
+++ b/WpfTest/Singleton.php
@@ -0,0 +1,18 @@
+<?php
+namespace WpfTest;
+
+abstract class Singleton {
+ private static array $instances;
+ protected function __construct() {
+ }
+ /**
+ * @return static the singleton instance
+ */
+ public static function getInstance() {
+ if (!isset(self::$instances[static::class])) {
+ //error_log('making a new ' . static::class);
+ self::$instances[static::class] = new static();
+ }
+ return self::$instances[static::class];
+ }
+} \ No newline at end of file
diff --git a/bin/migrate b/bin/migrate
new file mode 100755
index 0000000..29d2001
--- /dev/null
+++ b/bin/migrate
@@ -0,0 +1,10 @@
+#!/usr/bin/env php
+<?php
+
+require __DIR__ . '/../vendor/autoload.php';
+
+foreach (glob(__DIR__ . '/../migrations/*.php') as $file) {
+ require $file;
+}
+
+\WpfTest\Db\Migrator::getInstance()->migrate(); \ No newline at end of file
diff --git a/bin/newmigration b/bin/newmigration
new file mode 100755
index 0000000..9417726
--- /dev/null
+++ b/bin/newmigration
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+if [ $# -ne 1 ]; then
+ echo 'usage: bin/newmigration <name>'
+ exit 1
+fi
+
+cd "$(dirname "$0")" || exit 2
+cd ../migrations || exit 2
+
+version="$(date +'%Y%m%d_%H%M%S')"
+migratorFullClassName="$(find .. -name Migrator.php | sed 's_/_\\_g;s/^\.*//;s/\.php$//')"
+
+cat > "${version}_$1.php" << EOT
+<?php
+
+use $migratorFullClassName;
+
+Migrator::getInstance()->register($version, function (PDO \$db) {
+ // your code here
+});
+EOT
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..d349fcd
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,11 @@
+{
+ "require": {
+ "pda/pheanstalk": "^5.0",
+ "netresearch/jsonmapper": "^5.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "WpfTest\\": "WpfTest/"
+ }
+ }
+}
diff --git a/composer.lock b/composer.lock
new file mode 100644
index 0000000..aa55731
--- /dev/null
+++ b/composer.lock
@@ -0,0 +1,136 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "d05f7d22367afaf3265f5c2cde9e022e",
+ "packages": [
+ {
+ "name": "netresearch/jsonmapper",
+ "version": "v5.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/cweiske/jsonmapper.git",
+ "reference": "8c64d8d444a5d764c641ebe97e0e3bc72b25bf6c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8c64d8d444a5d764c641ebe97e0e3bc72b25bf6c",
+ "reference": "8c64d8d444a5d764c641ebe97e0e3bc72b25bf6c",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "ext-pcre": "*",
+ "ext-reflection": "*",
+ "ext-spl": "*",
+ "php": ">=7.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0 || ~10.0",
+ "squizlabs/php_codesniffer": "~3.5"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "JsonMapper": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "OSL-3.0"
+ ],
+ "authors": [
+ {
+ "name": "Christian Weiske",
+ "email": "cweiske@cweiske.de",
+ "homepage": "http://github.com/cweiske/jsonmapper/",
+ "role": "Developer"
+ }
+ ],
+ "description": "Map nested JSON structures onto PHP classes",
+ "support": {
+ "email": "cweiske@cweiske.de",
+ "issues": "https://github.com/cweiske/jsonmapper/issues",
+ "source": "https://github.com/cweiske/jsonmapper/tree/v5.0.0"
+ },
+ "time": "2024-09-08T10:20:00+00:00"
+ },
+ {
+ "name": "pda/pheanstalk",
+ "version": "v5.0.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/pheanstalk/pheanstalk.git",
+ "reference": "f02f69d568e5558325516ec335f21711466bfe7c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/pheanstalk/pheanstalk/zipball/f02f69d568e5558325516ec335f21711466bfe7c",
+ "reference": "f02f69d568e5558325516ec335f21711466bfe7c",
+ "shasum": ""
+ },
+ "require": {
+ "ext-mbstring": "*",
+ "php": ">=8.1.0"
+ },
+ "require-dev": {
+ "captainhook/plugin-composer": "^5.3",
+ "phpstan/extension-installer": "^1.1",
+ "phpstan/phpstan": "^1",
+ "phpstan/phpstan-phpunit": "^1.0",
+ "phpstan/phpstan-strict-rules": "^1.1",
+ "phpunit/phpunit": "^10",
+ "ramsey/conventional-commits": "^1.2",
+ "symplify/easy-coding-standard": "^11",
+ "vimeo/psalm": "^5"
+ },
+ "suggest": {
+ "ext-sockets": "Socket implementation works best for long running processes"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Pheanstalk\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Paul Annesley",
+ "email": "paul@annesley.cc",
+ "homepage": "http://paul.annesley.cc/",
+ "role": "Developer"
+ },
+ {
+ "name": "Sam Mousa",
+ "email": "sam@mousa.nl",
+ "role": "Maintainer"
+ }
+ ],
+ "description": "PHP client for beanstalkd queue",
+ "homepage": "https://github.com/pheanstalk/pheanstalk",
+ "keywords": [
+ "beanstalkd"
+ ],
+ "support": {
+ "issues": "https://github.com/pheanstalk/pheanstalk/issues",
+ "source": "https://github.com/pheanstalk/pheanstalk/tree/v5.0.7"
+ },
+ "time": "2024-10-04T13:56:46+00:00"
+ }
+ ],
+ "packages-dev": [],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": {},
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": {},
+ "platform-dev": {},
+ "plugin-api-version": "2.6.0"
+}
diff --git a/index.php b/index.php
index 54f1043..43603aa 100644
--- a/index.php
+++ b/index.php
@@ -1,17 +1,14 @@
<?php
-spl_autoload_register(function ($class) {
- $file = str_replace('\\', DIRECTORY_SEPARATOR, $class) . '.php';
- require $file;
-});
+require __DIR__ . '/vendor/autoload.php';
-function autoload_glob(string $pattern) {
+function require_all_glob(string $pattern) {
foreach (glob($pattern) as $filename) {
require $filename;
}
}
-autoload_glob('routes/*.php');
-autoload_glob('misc/*.php');
+require_all_glob('routes/*.php');
+require_all_glob('misc/*.php');
WpfTest\Router::getInstance()->processRequest();
diff --git a/migrations/20241207_183000_create_actor.php b/migrations/20241207_183000_create_actor.php
new file mode 100644
index 0000000..08de53d
--- /dev/null
+++ b/migrations/20241207_183000_create_actor.php
@@ -0,0 +1,40 @@
+<?php
+
+use \WpfTest\Db\Migrator;
+
+Migrator::getInstance()->register(20241207_183000, function (PDO $db) {
+ // create actor, endpoints, extensions
+ $db->exec(<<<'END'
+ CREATE TABLE actor (
+ id bigserial primary key not null,
+ uri text unique not null,
+ is_local boolean not null,
+ created timestamp with time zone not null,
+ modified timestamp with time zone,
+ homepage text,
+ handle text not null,
+ display_name text not null,
+ bio text,
+ pronouns text,
+ automated boolean not null default false,
+ request_to_follow boolean not null default false
+ );
+ CREATE TABLE actor_endpoints (
+ actor_id bigint unique not null references actor(id),
+ basic_feed text not null,
+ full_feed text,
+ follow text,
+ unfollow text,
+ block text,
+ unblock text,
+ subscribe text,
+ unsubscribe text
+ );
+ CREATE TABLE actor_extension (
+ actor_id bigint not null references actor(id),
+ uri text not null,
+ data jsonb not null,
+ unique(actor_id, uri)
+ );
+ END);
+});
diff --git a/routes/actor.php b/routes/actor.php
index 230f342..710adfa 100644
--- a/routes/actor.php
+++ b/routes/actor.php
@@ -1,24 +1,28 @@
<?php
-use WpfTest\HttpReturnStatus\NotFound;
+use WpfTest\HttpResponseStatus\NotFound;
+use WpfTest\Model\Actor;
use WpfTest\Router;
-Router::getInstance()->mount('/user/:username', function (array $args) {
- if ($args['username'] != 'winter') {
- throw new NotFound('i only know about winter');
+Router::getInstance()->mount('/user/:handle', function (array $args) {
+ $actor = Actor::findLocalByHandle($args['handle']);
+ if ($actor == null) {
+ throw new NotFound("i don't know any local user called " . $args['handle']);
}
- json_response([ // hardcoded much for testing
+ json_response([
'type' => 'actor',
- 'self' => path_to_uri('/user/winter'),
- 'created' => '2024-12-06T19:40:00+00:00',
- 'homepage' => path_to_uri('/winter'),
- 'handle' => 'winter',
- 'displayName' => 'winter!',
- 'bio' => 'winter on php',
- 'pronouns' => 'it/she',
- 'automated' => false,
+ 'dbgIsLocal' => $actor->isLocal,
+ 'self' => path_to_uri("/user/$actor->handle"),
+ 'created' => $actor->created?->format('c'),
+ 'modified' => $actor->modified?->format('c'),
+ 'homepage' => path_to_uri("/$actor->handle"),
+ 'handle' => $actor->handle,
+ 'displayName' => $actor->displayName,
+ 'bio' => $actor->bio,
+ 'pronouns' => $actor->pronouns,
+ 'automated' => $actor->automated,
'endpoints' => [
- 'basicFeed' => path_to_uri('/user/winter/basicFeed')
+ 'basicFeed' => path_to_uri("/user/$actor->handle/basicFeed")
]
]);
});
diff --git a/routes/nodeinfo.php b/routes/nodeinfo.php
index b507849..e85bca1 100644
--- a/routes/nodeinfo.php
+++ b/routes/nodeinfo.php
@@ -34,9 +34,13 @@ Router::getInstance()->mount('/nodeinfo/2.1', function (array $args) {
'metadata' => [
'nodeName' => "winter's pawsome test server",
'nodeDescription' => 'written in php i dunno',
- 'wpfExtensions' => [
- //
- ]
+ 'wpf' => [
+ 'extensions' => [
+ // 'https://whatever.example/ext/meow',
+ ],
+ // 'pushEndpoint' => path_to_uri('/push'),
+ // 'authEndpoint' => path_to_uri('/auth')
+ ],
]
], 'application/json; profile="http://nodeinfo.diaspora.software/ns/schema/2.1#"');
}); \ No newline at end of file
diff --git a/routes/webfinger.php b/routes/webfinger.php
index 5994529..4445cad 100644
--- a/routes/webfinger.php
+++ b/routes/webfinger.php
@@ -1,8 +1,8 @@
<?php
use WpfTest\GlobalConfig;
-use WpfTest\HttpReturnStatus\BadRequest;
-use WpfTest\HttpReturnStatus\NotFound;
+use WpfTest\HttpResponseStatus\BadRequest;
+use WpfTest\HttpResponseStatus\NotFound;
use WpfTest\Router;
Router::getInstance()->mount('/.well-known/webfinger', function (array $args) {
diff --git a/schema.sql b/schema.sql
new file mode 100644
index 0000000..e2442c7
--- /dev/null
+++ b/schema.sql
@@ -0,0 +1,9 @@
+CREATE TYPE enforce_unique AS ENUM ('unique');
+
+CREATE TABLE db_version (
+ enforce_unique enforce_unique UNIQUE NOT NULL,
+ db_version bigint NOT NULL
+);
+INSERT INTO db_version VALUES ('unique', 20241207180311);
+
+