diff options
-rwxr-xr-x | bin/db-migrate | 8 | ||||
-rw-r--r-- | composer.json | 2 | ||||
-rw-r--r-- | composer.lock | 514 | ||||
-rw-r--r-- | docker-compose.yml | 4 | ||||
-rw-r--r-- | migrations/20250819.php | 7 | ||||
-rw-r--r-- | src/Controllers/KeyController.php | 23 | ||||
-rw-r--r-- | src/Controllers/LoginController.php | 8 | ||||
-rwxr-xr-x | src/Controllers/RoomController.php | 54 | ||||
-rw-r--r-- | src/Controllers/ServerDiscoveryController.php | 7 | ||||
-rw-r--r-- | src/Controllers/ServerImplementationController.php | 5 | ||||
-rwxr-xr-x | src/Controllers/SyncController.php | 59 | ||||
-rw-r--r-- | src/Database.php | 9 | ||||
-rw-r--r-- | src/Events/Event.php | 16 | ||||
-rw-r--r-- | src/Events/PresenceEvent.php | 40 | ||||
-rw-r--r-- | src/Events/RoomMemberEvent.php | 33 | ||||
-rw-r--r-- | src/Events/RoomNameEvent.php | 33 | ||||
-rw-r--r-- | src/Router/Router.php | 7 | ||||
-rw-r--r-- | src/Router/routes_client_server.php | 6 | ||||
-rw-r--r-- | src/Singleton.php | 4 | ||||
-rw-r--r-- | src/Support/Logger.php | 50 | ||||
-rw-r--r-- | src/Support/Parser.php | 26 | ||||
-rw-r--r-- | src/Types/EventType.php | 10 | ||||
-rw-r--r-- | src/Types/PresenceState.php | 10 |
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"; +} |