summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xbin/db-migrate8
-rw-r--r--composer.json2
-rw-r--r--composer.lock514
-rw-r--r--docker-compose.yml4
-rw-r--r--migrations/20250819.php7
-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
23 files changed, 379 insertions, 556 deletions
diff --git a/bin/db-migrate b/bin/db-migrate
index 784aadc..c166fcd 100755
--- a/bin/db-migrate
+++ b/bin/db-migrate
@@ -15,7 +15,7 @@ $migrations = scandir($migrationsPath, SCANDIR_SORT_ASCENDING);
$appliedMigrations = [];
try {
- Database::getInstance()->query("select name from migrations")->fetchAll();
+ $appliedMigrations = array_column(Database::getInstance()->query("select name from migrations")->fetchAll(), "name");
} catch (\PDOException $exception) {
echo "migrations table doesn't exist yet.";
}
@@ -25,12 +25,14 @@ foreach ($migrations as $migration) {
continue;
}
- if (in_array($migration, $appliedMigrations)) {
+ $migrationName = basename($migration, ".php");
+
+ if (in_array($migrationName, $appliedMigrations)) {
continue;
}
$path = "$migrationsPath/$migration";
include $path;
- Database::getInstance()->query("insert into migrations (name) values (:name)", ["name" => $migration]);
+ Database::getInstance()->query("insert into migrations (name) values (:name)", ["name" => $migrationName]);
}
diff --git a/composer.json b/composer.json
index 951e156..f187ddf 100644
--- a/composer.json
+++ b/composer.json
@@ -11,7 +11,7 @@
"symfony/dotenv": "^7.3",
"symfony/http-foundation": "^7.3",
"symfony/routing": "^7.3",
- "symfony/expression-language": "^7.3"
+ "psr/log": "^3.0"
},
"require-dev": {
"guzzlehttp/guzzle": "^7.9",
diff --git a/composer.lock b/composer.lock
index 914b915..bb1e9cb 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,111 +4,9 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "c0a99656a34ec28749287e0a3fed54b4",
+ "content-hash": "4ddf0e945485466529ae4e7507dfb9fa",
"packages": [
{
- "name": "psr/cache",
- "version": "3.0.0",
- "source": {
- "type": "git",
- "url": "https://github.com/php-fig/cache.git",
- "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf",
- "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf",
- "shasum": ""
- },
- "require": {
- "php": ">=8.0.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.0.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Psr\\Cache\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "PHP-FIG",
- "homepage": "https://www.php-fig.org/"
- }
- ],
- "description": "Common interface for caching libraries",
- "keywords": [
- "cache",
- "psr",
- "psr-6"
- ],
- "support": {
- "source": "https://github.com/php-fig/cache/tree/3.0.0"
- },
- "time": "2021-02-03T23:26:27+00:00"
- },
- {
- "name": "psr/container",
- "version": "2.0.2",
- "source": {
- "type": "git",
- "url": "https://github.com/php-fig/container.git",
- "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963",
- "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
- "shasum": ""
- },
- "require": {
- "php": ">=7.4.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "2.0.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Psr\\Container\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "PHP-FIG",
- "homepage": "https://www.php-fig.org/"
- }
- ],
- "description": "Common Container Interface (PHP FIG PSR-11)",
- "homepage": "https://github.com/php-fig/container",
- "keywords": [
- "PSR-11",
- "container",
- "container-interface",
- "container-interop",
- "psr"
- ],
- "support": {
- "issues": "https://github.com/php-fig/container/issues",
- "source": "https://github.com/php-fig/container/tree/2.0.2"
- },
- "time": "2021-11-05T16:47:00+00:00"
- },
- {
"name": "psr/log",
"version": "3.0.2",
"source": {
@@ -159,184 +57,6 @@
"time": "2024-09-11T13:17:53+00:00"
},
{
- "name": "symfony/cache",
- "version": "v7.3.2",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/cache.git",
- "reference": "6621a2bee5373e3e972b2ae5dbedd5ac899d8cb6"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/cache/zipball/6621a2bee5373e3e972b2ae5dbedd5ac899d8cb6",
- "reference": "6621a2bee5373e3e972b2ae5dbedd5ac899d8cb6",
- "shasum": ""
- },
- "require": {
- "php": ">=8.2",
- "psr/cache": "^2.0|^3.0",
- "psr/log": "^1.1|^2|^3",
- "symfony/cache-contracts": "^3.6",
- "symfony/deprecation-contracts": "^2.5|^3.0",
- "symfony/service-contracts": "^2.5|^3",
- "symfony/var-exporter": "^6.4|^7.0"
- },
- "conflict": {
- "doctrine/dbal": "<3.6",
- "symfony/dependency-injection": "<6.4",
- "symfony/http-kernel": "<6.4",
- "symfony/var-dumper": "<6.4"
- },
- "provide": {
- "psr/cache-implementation": "2.0|3.0",
- "psr/simple-cache-implementation": "1.0|2.0|3.0",
- "symfony/cache-implementation": "1.1|2.0|3.0"
- },
- "require-dev": {
- "cache/integration-tests": "dev-master",
- "doctrine/dbal": "^3.6|^4",
- "predis/predis": "^1.1|^2.0",
- "psr/simple-cache": "^1.0|^2.0|^3.0",
- "symfony/clock": "^6.4|^7.0",
- "symfony/config": "^6.4|^7.0",
- "symfony/dependency-injection": "^6.4|^7.0",
- "symfony/filesystem": "^6.4|^7.0",
- "symfony/http-kernel": "^6.4|^7.0",
- "symfony/messenger": "^6.4|^7.0",
- "symfony/var-dumper": "^6.4|^7.0"
- },
- "type": "library",
- "autoload": {
- "psr-4": {
- "Symfony\\Component\\Cache\\": ""
- },
- "classmap": [
- "Traits/ValueWrapper.php"
- ],
- "exclude-from-classmap": [
- "/Tests/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Nicolas Grekas",
- "email": "p@tchwork.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Provides extended PSR-6, PSR-16 (and tags) implementations",
- "homepage": "https://symfony.com",
- "keywords": [
- "caching",
- "psr6"
- ],
- "support": {
- "source": "https://github.com/symfony/cache/tree/v7.3.2"
- },
- "funding": [
- {
- "url": "https://symfony.com/sponsor",
- "type": "custom"
- },
- {
- "url": "https://github.com/fabpot",
- "type": "github"
- },
- {
- "url": "https://github.com/nicolas-grekas",
- "type": "github"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
- "type": "tidelift"
- }
- ],
- "time": "2025-07-30T17:13:41+00:00"
- },
- {
- "name": "symfony/cache-contracts",
- "version": "v3.6.0",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/cache-contracts.git",
- "reference": "5d68a57d66910405e5c0b63d6f0af941e66fc868"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/5d68a57d66910405e5c0b63d6f0af941e66fc868",
- "reference": "5d68a57d66910405e5c0b63d6f0af941e66fc868",
- "shasum": ""
- },
- "require": {
- "php": ">=8.1",
- "psr/cache": "^3.0"
- },
- "type": "library",
- "extra": {
- "thanks": {
- "url": "https://github.com/symfony/contracts",
- "name": "symfony/contracts"
- },
- "branch-alias": {
- "dev-main": "3.6-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Contracts\\Cache\\": ""
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Nicolas Grekas",
- "email": "p@tchwork.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Generic abstractions related to caching",
- "homepage": "https://symfony.com",
- "keywords": [
- "abstractions",
- "contracts",
- "decoupling",
- "interfaces",
- "interoperability",
- "standards"
- ],
- "support": {
- "source": "https://github.com/symfony/cache-contracts/tree/v3.6.0"
- },
- "funding": [
- {
- "url": "https://symfony.com/sponsor",
- "type": "custom"
- },
- {
- "url": "https://github.com/fabpot",
- "type": "github"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
- "type": "tidelift"
- }
- ],
- "time": "2025-03-13T15:25:07+00:00"
- },
- {
"name": "symfony/deprecation-contracts",
"version": "v3.6.0",
"source": {
@@ -482,74 +202,6 @@
"time": "2025-07-10T08:29:33+00:00"
},
{
- "name": "symfony/expression-language",
- "version": "v7.3.2",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/expression-language.git",
- "reference": "32d2d19c62e58767e6552166c32fb259975d2b23"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/expression-language/zipball/32d2d19c62e58767e6552166c32fb259975d2b23",
- "reference": "32d2d19c62e58767e6552166c32fb259975d2b23",
- "shasum": ""
- },
- "require": {
- "php": ">=8.2",
- "symfony/cache": "^6.4|^7.0",
- "symfony/deprecation-contracts": "^2.5|^3",
- "symfony/service-contracts": "^2.5|^3"
- },
- "type": "library",
- "autoload": {
- "psr-4": {
- "Symfony\\Component\\ExpressionLanguage\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Provides an engine that can compile and evaluate expressions",
- "homepage": "https://symfony.com",
- "support": {
- "source": "https://github.com/symfony/expression-language/tree/v7.3.2"
- },
- "funding": [
- {
- "url": "https://symfony.com/sponsor",
- "type": "custom"
- },
- {
- "url": "https://github.com/fabpot",
- "type": "github"
- },
- {
- "url": "https://github.com/nicolas-grekas",
- "type": "github"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
- "type": "tidelift"
- }
- ],
- "time": "2025-07-10T08:29:33+00:00"
- },
- {
"name": "symfony/http-foundation",
"version": "v7.3.2",
"source": {
@@ -873,170 +525,6 @@
}
],
"time": "2025-07-15T11:36:08+00:00"
- },
- {
- "name": "symfony/service-contracts",
- "version": "v3.6.0",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/service-contracts.git",
- "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4",
- "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4",
- "shasum": ""
- },
- "require": {
- "php": ">=8.1",
- "psr/container": "^1.1|^2.0",
- "symfony/deprecation-contracts": "^2.5|^3"
- },
- "conflict": {
- "ext-psr": "<1.1|>=2"
- },
- "type": "library",
- "extra": {
- "thanks": {
- "url": "https://github.com/symfony/contracts",
- "name": "symfony/contracts"
- },
- "branch-alias": {
- "dev-main": "3.6-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Contracts\\Service\\": ""
- },
- "exclude-from-classmap": [
- "/Test/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Nicolas Grekas",
- "email": "p@tchwork.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Generic abstractions related to writing services",
- "homepage": "https://symfony.com",
- "keywords": [
- "abstractions",
- "contracts",
- "decoupling",
- "interfaces",
- "interoperability",
- "standards"
- ],
- "support": {
- "source": "https://github.com/symfony/service-contracts/tree/v3.6.0"
- },
- "funding": [
- {
- "url": "https://symfony.com/sponsor",
- "type": "custom"
- },
- {
- "url": "https://github.com/fabpot",
- "type": "github"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
- "type": "tidelift"
- }
- ],
- "time": "2025-04-25T09:37:31+00:00"
- },
- {
- "name": "symfony/var-exporter",
- "version": "v7.3.2",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/var-exporter.git",
- "reference": "05b3e90654c097817325d6abd284f7938b05f467"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/var-exporter/zipball/05b3e90654c097817325d6abd284f7938b05f467",
- "reference": "05b3e90654c097817325d6abd284f7938b05f467",
- "shasum": ""
- },
- "require": {
- "php": ">=8.2",
- "symfony/deprecation-contracts": "^2.5|^3"
- },
- "require-dev": {
- "symfony/property-access": "^6.4|^7.0",
- "symfony/serializer": "^6.4|^7.0",
- "symfony/var-dumper": "^6.4|^7.0"
- },
- "type": "library",
- "autoload": {
- "psr-4": {
- "Symfony\\Component\\VarExporter\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Nicolas Grekas",
- "email": "p@tchwork.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Allows exporting any serializable PHP data structure to plain PHP code",
- "homepage": "https://symfony.com",
- "keywords": [
- "clone",
- "construct",
- "export",
- "hydrate",
- "instantiate",
- "lazy-loading",
- "proxy",
- "serialize"
- ],
- "support": {
- "source": "https://github.com/symfony/var-exporter/tree/v7.3.2"
- },
- "funding": [
- {
- "url": "https://symfony.com/sponsor",
- "type": "custom"
- },
- {
- "url": "https://github.com/fabpot",
- "type": "github"
- },
- {
- "url": "https://github.com/nicolas-grekas",
- "type": "github"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
- "type": "tidelift"
- }
- ],
- "time": "2025-07-10T08:47:49+00:00"
}
],
"packages-dev": [
diff --git a/docker-compose.yml b/docker-compose.yml
index 073a7c9..ea5668f 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,8 +1,6 @@
services:
db:
image: postgres
- ports:
- - "5432:5432"
environment:
- "POSTGRES_DB=${DB_NAME}"
- "POSTGRES_USER=${DB_USER}"
@@ -14,6 +12,7 @@ services:
build: "docker/php"
volumes:
- "./:/var/www/html"
+ - "/etc/localtime:/etc/localtime"
web:
build: "docker/nginx"
@@ -21,6 +20,7 @@ services:
- "8080:80"
volumes:
- "./:/var/www/html"
+ - "/etc/localtime:/etc/localtime"
adminer:
image: adminer
diff --git a/migrations/20250819.php b/migrations/20250819.php
index 061b59c..5e4eaa4 100644
--- a/migrations/20250819.php
+++ b/migrations/20250819.php
@@ -47,3 +47,10 @@ Database::getInstance()->query(<<<SQL
foreign key (user_id, device_id) references devices(user_id, id)
);
SQL);
+
+Database::getInstance()->query(<<<SQL
+ create table if not exists "rooms" (
+ "id" varchar(255) primary key,
+ "name" varchar(255) not null
+ );
+SQL);
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";
+}