summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDaniel Weipert <git@mail.dweipert.de>2025-09-11 13:19:21 +0200
committerDaniel Weipert <git@mail.dweipert.de>2025-09-11 13:19:21 +0200
commitb1b101fd98c8b4354a4e0c73e867d817466de30e (patch)
tree763e6d3dab13a2af8c324f7f879c5874dced76f2 /src
parentdb014ebf9f8f84a1a0d0972298e70bf29e57c37e (diff)
sync, rooms, events, etc
Diffstat (limited to 'src')
-rw-r--r--src/Controllers/KeyController.php23
-rw-r--r--src/Controllers/LoginController.php8
-rwxr-xr-xsrc/Controllers/RoomController.php54
-rw-r--r--src/Controllers/ServerDiscoveryController.php7
-rw-r--r--src/Controllers/ServerImplementationController.php5
-rwxr-xr-xsrc/Controllers/SyncController.php59
-rw-r--r--src/Database.php9
-rw-r--r--src/Events/Event.php16
-rw-r--r--src/Events/PresenceEvent.php40
-rw-r--r--src/Events/RoomMemberEvent.php33
-rw-r--r--src/Events/RoomNameEvent.php33
-rw-r--r--src/Router/Router.php7
-rw-r--r--src/Router/routes_client_server.php6
-rw-r--r--src/Singleton.php4
-rw-r--r--src/Support/Logger.php50
-rw-r--r--src/Support/Parser.php26
-rw-r--r--src/Types/EventType.php10
-rw-r--r--src/Types/PresenceState.php10
18 files changed, 363 insertions, 37 deletions
diff --git a/src/Controllers/KeyController.php b/src/Controllers/KeyController.php
index 5050d9b..53e9ff4 100644
--- a/src/Controllers/KeyController.php
+++ b/src/Controllers/KeyController.php
@@ -12,7 +12,7 @@ use Symfony\Component\HttpFoundation\JsonResponse;
class KeyController
{
- public function server(): Response
+ public function server(Request $request): Response
{
return new JsonResponse([
"server" => [
@@ -22,26 +22,31 @@ class KeyController
]);
}
- public function upload(): Response
+ /**
+ * POST /_matrix/client/v3/keys/upload
+ */
+ public function upload(Request $request): Response
{
- $request = Request::createFromGlobals();
+ $body = json_decode($request->getContent(), true);
+ RequestValidator::validateJson();
return new JsonResponse([
- "one_time_key_counts" => [],
+ "one_time_key_counts" => count($body["one_time_keys"]),
]);
}
- public function query(string $serverName): Response
- {}
+ public function query(Request $request): Response
+ {
+ $serverName = $request->attributes->get("serverName");
+ }
/**
* POST /_matrix/client/v3/refresh
*/
- public function refresh(): Response
+ public function refresh(Request $request): Response
{
- $request = Request::createFromGlobals();
- RequestValidator::validateJson();
$body = json_decode($request->getContent(), true);
+ RequestValidator::validateJson();
$tokens = Tokens::fetchWithRefreshToken($body["refresh_token"]);
diff --git a/src/Controllers/LoginController.php b/src/Controllers/LoginController.php
index fd48f25..15f1583 100644
--- a/src/Controllers/LoginController.php
+++ b/src/Controllers/LoginController.php
@@ -22,7 +22,7 @@ class LoginController
/**
* GET /_matrix/client/r0/login
*/
- public function supportedLoginTypes(): Response
+ public function supportedLoginTypes(Request $request): Response
{
return new JsonResponse([
"flows" => [
@@ -34,9 +34,8 @@ class LoginController
/**
* POST /_matrix/client/v3/login
*/
- public function login(): Response
+ public function login(Request $request): Response
{
- $request = Request::createFromGlobals();
$body = json_decode($request->getContent(), true);
RequestValidator::validateJson();
@@ -101,9 +100,8 @@ class LoginController
/**
* POST /_matrix/client/v3/register
*/
- public function register(): Response
+ public function register(Request $request): Response
{
- $request = Request::createFromGlobals();
$body = json_decode($request->getContent(), true);
RequestValidator::validateJson();
diff --git a/src/Controllers/RoomController.php b/src/Controllers/RoomController.php
new file mode 100755
index 0000000..1067d29
--- /dev/null
+++ b/src/Controllers/RoomController.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace App\Controllers;
+
+use App\Database;
+use App\Errors\AppException;
+use App\Errors\ErrorCode;
+use App\Support\Parser;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\JsonResponse;
+
+class RoomController
+{
+ /**
+ * GET /_matrix/client/v3/directory/room/{roomAlias}
+ *
+ * @see https://spec.matrix.org/v1.15/client-server-api/#get_matrixclientv3directoryroomroomalias
+ */
+ public function resolveAlias(Request $request): Response
+ {
+ $alias = $request->attributes->get("roomAlias");
+
+ $roomAlias = Parser::parseRoomAlias($alias); # TODO: on parse error => 400
+ $roomId = null;
+
+ if ($roomAlias["server"] != $_ENV["DOMAIN"]) {
+ # TODO: federation API resolve
+ $roomId = -1;
+ }
+ else {
+ $room = Database::getInstance()->query(<<<SQL
+ select id from rooms where name = :name
+ SQL, [
+ "name" => $roomAlias["name"],
+ ])->fetch();
+
+ $roomId = $room["id"] ?? null;
+ }
+
+ if (empty($roomId)) {
+ throw new AppException(
+ ErrorCode::NOT_FOUND,
+ "Room alias $alias not found.",
+ Response::HTTP_NOT_FOUND
+ );
+ }
+
+ return new JsonResponse([
+ "room_id" => $roomId,
+ "servers" => [],
+ ]);
+ }
+}
diff --git a/src/Controllers/ServerDiscoveryController.php b/src/Controllers/ServerDiscoveryController.php
index c4e5ea7..f3b96b2 100644
--- a/src/Controllers/ServerDiscoveryController.php
+++ b/src/Controllers/ServerDiscoveryController.php
@@ -2,19 +2,20 @@
namespace App\Controllers;
+use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
class ServerDiscoveryController
{
- public function server(): Response
+ public function server(Request $request): Response
{
return new JsonResponse([
"m.server" => "$_ENV[DOMAIN]:443",
]);
}
- public function client(): Response
+ public function client(Request $request): Response
{
return new JsonResponse([
"m.homeserver" => [
@@ -23,7 +24,7 @@ class ServerDiscoveryController
]);
}
- public function support(): Response
+ public function support(Request $request): Response
{
return new JsonResponse([
"contacts" => [],
diff --git a/src/Controllers/ServerImplementationController.php b/src/Controllers/ServerImplementationController.php
index e168a65..93b9a3f 100644
--- a/src/Controllers/ServerImplementationController.php
+++ b/src/Controllers/ServerImplementationController.php
@@ -2,12 +2,13 @@
namespace App\Controllers;
+use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
class ServerImplementationController
{
- public function version(): Response
+ public function version(Request $request): Response
{
return new JsonResponse([
"server" => [
@@ -17,7 +18,7 @@ class ServerImplementationController
]);
}
- public function versions(): Response
+ public function versions(Request $request): Response
{
return new JsonResponse([
"versions" => [
diff --git a/src/Controllers/SyncController.php b/src/Controllers/SyncController.php
index 19da1b5..76b6a26 100755
--- a/src/Controllers/SyncController.php
+++ b/src/Controllers/SyncController.php
@@ -2,25 +2,72 @@
namespace App\Controllers;
+use App\Database;
use App\Errors\UnauthorizedError;
+use App\Events\PresenceEvent;
+use App\Types\PresenceState;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
class SyncController
{
- public function sync(): Response
+ /**
+ * GET /_matrix/client/v3/sync
+ *
+ * @see https://spec.matrix.org/v1.15/client-server-api/#get_matrixclientv3sync
+ * @see https://spec.matrix.org/v1.15/client-server-api/#extensions-to-sync
+ */
+ public function sync(Request $request): Response
{
- $request = Request::createFromGlobals();
+ $accessToken = str_replace("Bearer ", "", $request->headers->get("authorization") ?: "");
+ $user = Database::getInstance()->query(<<<SQL
+ select users.* from users left join tokens on tokens.user_id = users.id where tokens.access_token=:access_token
+ SQL, [
+ "access_token" => $accessToken,
+ ])->fetch();
- if ($request->headers->get("authorization") != "Bearer abc123") {
- # TODO: get user based on bearer token
+ # TODO: token validation
+
+ if (empty($user)) {
throw new UnauthorizedError();
}
+ $filter = $request->query->get("filter", "");
+ $syncFullState = $request->query->get("full_state", false);
+ $setPresence = PresenceState::tryFrom($request->query->get("set_presence") ?? "") ?? PresenceState::ONLINE;
+ $since = $request->query->get("since", "");
+ $timeout = $request->query->get("timeout", 0);
+
return new JsonResponse([
- "account_data" => [],
- "next_batch" => "",
+ "account_data" => [
+ "events" => [
+
+ ],
+ ],
+
+ "device_lists" => [],
+
+ "device_one_time_keys_count" => 10,
+
+ "next_batch" => "next_batch_id",
+
+ "presence" => [
+ "events" => [
+ (new PresenceEvent(sender: $user["id"]))->toJsonEncodeable(),
+ ],
+ ],
+
+ "rooms" => [
+ "invite" => [],
+ "join" => [],
+ "knock" => [],
+ "leave" => [],
+ ],
+
+ "to_device" => [
+ "events" => [],
+ ],
]);
}
}
diff --git a/src/Database.php b/src/Database.php
index 3fff863..e84abc1 100644
--- a/src/Database.php
+++ b/src/Database.php
@@ -20,7 +20,14 @@ class Database
$user = $_ENV['DB_USER'];
$password = $_ENV['DB_PASSWORD'];
- $this->connection = new \PDO("pgsql:host=$host;port=$port;dbname=$dbname", $user, $password);
+ $this->connection = new \PDO(
+ "pgsql:host=$host;port=$port;dbname=$dbname",
+ $user,
+ $password,
+ [
+ \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
+ ]
+ );
}
/**
diff --git a/src/Events/Event.php b/src/Events/Event.php
new file mode 100644
index 0000000..cab1b14
--- /dev/null
+++ b/src/Events/Event.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace App\Events;
+
+use App\Types\EventType;
+
+abstract class Event
+{
+ public function __construct(
+ protected EventType $type,
+ )
+ {}
+
+ abstract public function fromJson(string $json): static;
+ abstract public function toJsonEncodeable(): array;
+}
diff --git a/src/Events/PresenceEvent.php b/src/Events/PresenceEvent.php
new file mode 100644
index 0000000..e3a54b3
--- /dev/null
+++ b/src/Events/PresenceEvent.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace App\Events;
+
+use App\Types\EventType;
+use App\Types\PresenceState;
+
+class PresenceEvent extends Event
+{
+ public function __construct(
+ private string $sender,
+ private string $avatarUrl = "mxc://localhost/wefuiwegh8742w",
+ private int $lastActiveAgo = 1234,
+ private bool $currentlyActive = false,
+ private PresenceState $presence = PresenceState::ONLINE,
+ private string $statusMessage = "",
+ )
+ {
+ parent::__construct(EventType::PRESENCE);
+ }
+
+ public function fromJson(string $json): static
+ {
+ }
+
+ public function toJsonEncodeable(): array
+ {
+ return [
+ "type" => $this->type,
+ "sender" => $this->sender,
+ "content" => [
+ "avatar_url" => $this->avatarUrl,
+ "currently_active" => $this->currentlyActive,
+ "last_active_ago" => $this->lastActiveAgo,
+ "presence" => $this->presence,
+ "status_msg" => $this->statusMessage,
+ ],
+ ];
+ }
+}
diff --git a/src/Events/RoomMemberEvent.php b/src/Events/RoomMemberEvent.php
new file mode 100644
index 0000000..02793ea
--- /dev/null
+++ b/src/Events/RoomMemberEvent.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace App\Events;
+
+use App\Types\EventType;
+
+class RoomMemberEvent extends Event
+{
+ public function __construct(
+ private string $sender,
+ private string $stateKey,
+ private string $membership,
+ )
+ {
+ parent::__construct(EventType::ROOM_MEMBER);
+ }
+
+ public function fromJson(string $json): static
+ {
+ }
+
+ public function toJsonEncodeable(): array
+ {
+ return [
+ "type" => $this->type,
+ "sender" => $this->sender,
+ "state_key" => $this->stateKey,
+ "content" => [
+ "membership" => $this->membership,
+ ],
+ ];
+ }
+}
diff --git a/src/Events/RoomNameEvent.php b/src/Events/RoomNameEvent.php
new file mode 100644
index 0000000..59abbe2
--- /dev/null
+++ b/src/Events/RoomNameEvent.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace App\Events;
+
+use App\Types\EventType;
+
+class RoomNameEvent extends Event
+{
+ public function __construct(
+ private string $sender,
+ private string $stateKey,
+ private string $name,
+ )
+ {
+ parent::__construct(EventType::ROOM_NAME);
+ }
+
+ public function fromJson(string $json): static
+ {
+ }
+
+ public function toJsonEncodeable(): array
+ {
+ return [
+ "type" => $this->type,
+ "sender" => $this->sender,
+ "state_key" => $this->stateKey,
+ "content" => [
+ "name" => $this->name,
+ ],
+ ];
+ }
+}
diff --git a/src/Router/Router.php b/src/Router/Router.php
index 61abed1..534b7f7 100644
--- a/src/Router/Router.php
+++ b/src/Router/Router.php
@@ -59,7 +59,12 @@ class Router
$class = $match["_controller"][0];
$method = $match["_controller"][1];
- return (new $class)->$method();
+ $request->attributes->add(array_diff_key(
+ $match,
+ array_flip(["_controller", "_route"])
+ ));
+
+ return (new $class)->$method($request);
} catch (Exception $exception) {
return ErrorResponse::fromException($exception);
} catch (ResourceNotFoundException $exception) {
diff --git a/src/Router/routes_client_server.php b/src/Router/routes_client_server.php
index 8979af5..6e7bb8d 100644
--- a/src/Router/routes_client_server.php
+++ b/src/Router/routes_client_server.php
@@ -4,6 +4,7 @@ namespace App\Router;
use App\Controllers\KeyController;
use App\Controllers\LoginController;
+use App\Controllers\RoomController;
use App\Controllers\ServerDiscoveryController;
use App\Controllers\ServerImplementationController;
use App\Controllers\SyncController;
@@ -51,4 +52,9 @@ return function (RouteConfigurator $routes): void
->add("matrix_client_v3_refresh", "/_matrix/client/v3/refresh")
->controller([KeyController::class, "refresh"])
->methods(["POST"]);
+
+ $routes
+ ->add("matrix_client_v3_directory_room_alias_get", "/_matrix/client/v3/directory/room/{roomAlias}")
+ ->controller([RoomController::class, "resolveAlias"])
+ ->methods(["GET"]);
};
diff --git a/src/Singleton.php b/src/Singleton.php
index 864da59..3238248 100644
--- a/src/Singleton.php
+++ b/src/Singleton.php
@@ -6,10 +6,10 @@ trait Singleton
{
private static self $instance;
- public static function getInstance(): self
+ public static function getInstance(): static
{
if (! isset(self::$instance)) {
- self::$instance = new self();
+ self::$instance = new static();
}
return self::$instance;
diff --git a/src/Support/Logger.php b/src/Support/Logger.php
index fb86166..c806157 100644
--- a/src/Support/Logger.php
+++ b/src/Support/Logger.php
@@ -2,22 +2,64 @@
namespace App\Support;
+use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
-class Logger
+class Logger implements LoggerInterface
{
public static function logRequestToFile(Request $request): void
{
- $basePath = dirname(dirname(__DIR__)) . "/.phpunit.cache/" . str_replace("/", "_", $request->getPathInfo());
+ $basePath = dirname(dirname(__DIR__)) . "/.cache/log/" . str_replace("/", "_", $request->getPathInfo());
file_put_contents(
$basePath . "-body.json",
- $request->getContent()
+ json_encode(json_decode($request->getContent()), JSON_PRETTY_PRINT)
+ );
+
+ file_put_contents(
+ $basePath . "-query.json",
+ json_encode($request->query->all(), JSON_PRETTY_PRINT)
);
file_put_contents(
$basePath . "-header.json",
- json_encode($request->headers->all())
+ json_encode($request->headers->all(), JSON_PRETTY_PRINT)
);
}
+
+ public function emergency($message, array $context = []): void
+ {
+ }
+
+ public function alert($message, array $context = []): void
+ {
+ }
+
+ public function critical($message, array $context = []): void
+ {
+ }
+
+ public function error($message, array $context = []): void
+ {
+ }
+
+ public function warning($message, array $context = []): void
+ {
+ }
+
+ public function notice($message, array $context = []): void
+ {
+ }
+
+ public function info($message, array $context = []): void
+ {
+ }
+
+ public function debug($message, array $context = []): void
+ {
+ }
+
+ public function log($level, $message, array $context = []): void
+ {
+ }
}
diff --git a/src/Support/Parser.php b/src/Support/Parser.php
index d850de1..02ec251 100644
--- a/src/Support/Parser.php
+++ b/src/Support/Parser.php
@@ -4,10 +4,10 @@ namespace App\Support;
class Parser
{
- /**
- * @return array<string, string>
- */
- public static function parseUser(string $user): array
+ /**
+ * @return array<string, string>
+ */
+ public static function parseUser(string $user): array
{
$username = $user;
$server = "";
@@ -24,4 +24,22 @@ class Parser
"server" => $server,
];
}
+
+ /**
+ * @return array<string, string>
+ */
+ public static function parseRoomAlias(string $alias): array
+ {
+ $name = "";
+ $server = "";
+
+ $parts = explode(":", $alias);
+ $name = substr($parts[0], 1);
+ $server = $parts[1];
+
+ return [
+ "name" => $name,
+ "server" => $server,
+ ];
+ }
}
diff --git a/src/Types/EventType.php b/src/Types/EventType.php
new file mode 100644
index 0000000..a79debc
--- /dev/null
+++ b/src/Types/EventType.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace App\Types;
+
+enum EventType: string
+{
+ case PRESENCE = "m.presence";
+ case ROOM_NAME = "m.room.name";
+ case ROOM_MEMBER = "m.room.member";
+}
diff --git a/src/Types/PresenceState.php b/src/Types/PresenceState.php
new file mode 100644
index 0000000..26eeac6
--- /dev/null
+++ b/src/Types/PresenceState.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace App\Types;
+
+enum PresenceState: string
+{
+ case ONLINE = "online";
+ case OFFLINE = "offline";
+ case UNAVAILABLE = "unavailable";
+}