From ac5bc22817a846b5f6b6df9ebf1e0684fbe21de8 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 4 Oct 2024 23:23:18 +0100 Subject: [PATCH 1/3] feat: ping endpoint --- app/config/collections.php | 22 ++++++++ app/controllers/general.php | 46 ++++++++++++++++ .../Utopia/Response/Model/Project.php | 12 ++++ tests/e2e/General/PingTest.php | 55 +++++++++++++++++++ 4 files changed, 135 insertions(+) create mode 100644 tests/e2e/General/PingTest.php diff --git a/app/config/collections.php b/app/config/collections.php index 1eb286cf8f..4a19915b1b 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -4541,6 +4541,28 @@ $consoleCollections = array_merge([ 'array' => false, 'filters' => [], ], + [ + '$id' => ID::custom('pingCount'), + 'type' => Database::VAR_INTEGER, + 'format' => '', + 'size' => 0, + 'signed' => false, + 'required' => false, + 'default' => 0, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('pingedAt'), + 'type' => Database::VAR_DATETIME, + 'format' => '', + 'size' => 0, + 'signed' => false, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => ['datetime'], + ] ], 'indexes' => [ [ diff --git a/app/controllers/general.php b/app/controllers/general.php index 04554a940e..7395a479a4 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -25,10 +25,12 @@ use Utopia\App; use Utopia\CLI\Console; use Utopia\Config\Config; use Utopia\Database\Database; +use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; +use Utopia\Database\Validator\UID; use Utopia\Domains\Domain; use Utopia\DSN\DSN; use Utopia\Locale\Locale; @@ -1048,6 +1050,50 @@ App::get('/.well-known/acme-challenge/*') include_once __DIR__ . '/shared/api.php'; include_once __DIR__ . '/shared/api/auth.php'; +App::get('/v1/ping') + ->groups(['api', 'general']) + ->desc('Test the connection between the Appwrite and the SDK.') + ->label('scope', 'public') + ->label('event', 'projects.[projectId].ping') + ->param('projectId', '', new UID(), 'Project unique ID.') + ->inject('response') + ->inject('console') + ->inject('dbForConsole') + ->inject('queueForEvents') + ->action(function (string $projectId, Response $response, Document $project, Database $dbForConsole, Event $queueForEvents) { + if (empty($projectId) || $projectId === 'console') { + throw new AppwriteException(AppwriteException::PROJECT_NOT_FOUND); + } + + Console::log('Ping' . json_encode(['projectId' => $projectId], JSON_PRETTY_PRINT)); + + $project = Authorization::skip(function () use ($dbForConsole, $projectId) { + return $dbForConsole->getDocument('projects', $projectId); + }); + + if ($project->isEmpty()) { + Console::log('Ping' . json_encode($project, JSON_PRETTY_PRINT)); + throw new AppwriteException(AppwriteException::PROJECT_NOT_FOUND); + } + + $pingCount = $project->getAttribute('pingCount', 0) + 1; + $pingedAt = DateTime::now(); + + $project + ->setAttribute('pingCount', $pingCount) + ->setAttribute('pingedAt', $pingedAt); + + Authorization::skip(function () use ($dbForConsole, $project) { + $dbForConsole->updateDocument('projects', $project->getId(), $project); + }); + + $queueForEvents + ->setParam('projectId', $projectId) + ->setPayload($response->output($project, Response::MODEL_PROJECT)); + + $response->text('Pong!'); + }); + App::wildcard() ->groups(['api']) ->label('scope', 'global') diff --git a/src/Appwrite/Utopia/Response/Model/Project.php b/src/Appwrite/Utopia/Response/Model/Project.php index 80214aaa73..e1d0105587 100644 --- a/src/Appwrite/Utopia/Response/Model/Project.php +++ b/src/Appwrite/Utopia/Response/Model/Project.php @@ -234,6 +234,18 @@ class Project extends Model 'default' => '', 'example' => 'tls', ]) + ->addRule('pingCount', [ + 'type' => self::TYPE_INTEGER, + 'description' => 'Number of times the ping was received for this project.', + 'default' => 0, + 'example' => 1, + ]) + ->addRule('pingedAt', [ + 'type' => self::TYPE_DATETIME, + 'description' => 'Last ping datetime in ISO 8601 format.', + 'default' => '', + 'example' => self::TYPE_DATETIME_EXAMPLE, + ]) ; $services = Config::getParam('services', []); diff --git a/tests/e2e/General/PingTest.php b/tests/e2e/General/PingTest.php new file mode 100644 index 0000000000..c1199841fb --- /dev/null +++ b/tests/e2e/General/PingTest.php @@ -0,0 +1,55 @@ +client->call(Client::METHOD_GET, '/ping', [], [ + 'projectId' => $this->getProject()['$id'], + ]); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('Pong!', $response['body']); + + // With user session + $response = $this->client->call(Client::METHOD_GET, '/ping', $this->getHeaders(), [ + 'projectId' => $this->getProject()['$id'], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('Pong!', $response['body']); + + // With API key + $response = $this->client->call(Client::METHOD_GET, '/ping', [ + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'projectId' => $this->getProject()['$id'], + ]); + + /** + * Test for FAILURE + */ + // Fake project ID + $response = $this->client->call(Client::METHOD_GET, '/ping', \array_merge([ + 'origin' => 'http://localhost', + ]), [ + 'projectId' => 'fake-project-id', + ]); + + $this->assertEquals(404, $response['headers']['status-code']); + $this->assertNotContains('Pong!', $response['body']); + } +} From 6dab42e59a793b6b570b97dabb7fe5f9cc1712fc Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Mon, 7 Oct 2024 14:00:19 +0100 Subject: [PATCH 2/3] chore: use project injectable --- app/controllers/general.php | 14 +------ tests/e2e/General/PingTest.php | 19 ++++----- .../Realtime/RealtimeConsoleClientTest.php | 40 +++++++++++++++++++ 3 files changed, 50 insertions(+), 23 deletions(-) diff --git a/app/controllers/general.php b/app/controllers/general.php index 7395a479a4..227745b028 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -1061,18 +1061,7 @@ App::get('/v1/ping') ->inject('dbForConsole') ->inject('queueForEvents') ->action(function (string $projectId, Response $response, Document $project, Database $dbForConsole, Event $queueForEvents) { - if (empty($projectId) || $projectId === 'console') { - throw new AppwriteException(AppwriteException::PROJECT_NOT_FOUND); - } - - Console::log('Ping' . json_encode(['projectId' => $projectId], JSON_PRETTY_PRINT)); - - $project = Authorization::skip(function () use ($dbForConsole, $projectId) { - return $dbForConsole->getDocument('projects', $projectId); - }); - if ($project->isEmpty()) { - Console::log('Ping' . json_encode($project, JSON_PRETTY_PRINT)); throw new AppwriteException(AppwriteException::PROJECT_NOT_FOUND); } @@ -1081,13 +1070,14 @@ App::get('/v1/ping') $project ->setAttribute('pingCount', $pingCount) - ->setAttribute('pingedAt', $pingedAt); + ->setAttribute('pingedAt', $pingedAt); Authorization::skip(function () use ($dbForConsole, $project) { $dbForConsole->updateDocument('projects', $project->getId(), $project); }); $queueForEvents + ->setProject($project) ->setParam('projectId', $projectId) ->setPayload($response->output($project, Response::MODEL_PROJECT)); diff --git a/tests/e2e/General/PingTest.php b/tests/e2e/General/PingTest.php index c1199841fb..96db658cc3 100644 --- a/tests/e2e/General/PingTest.php +++ b/tests/e2e/General/PingTest.php @@ -18,25 +18,24 @@ class PingTest extends Scope * Test for SUCCESS */ // Without user session - $response = $this->client->call(Client::METHOD_GET, '/ping', [], [ - 'projectId' => $this->getProject()['$id'], + $response = $this->client->call(Client::METHOD_GET, '/ping', [ + 'x-appwrite-project' => $this->getProject()['$id'], ]); $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals('Pong!', $response['body']); // With user session - $response = $this->client->call(Client::METHOD_GET, '/ping', $this->getHeaders(), [ - 'projectId' => $this->getProject()['$id'], - ]); + $response = $this->client->call(Client::METHOD_GET, '/ping', array_merge([ + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals('Pong!', $response['body']); // With API key $response = $this->client->call(Client::METHOD_GET, '/ping', [ + 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'projectId' => $this->getProject()['$id'], ]); /** @@ -44,10 +43,8 @@ class PingTest extends Scope */ // Fake project ID $response = $this->client->call(Client::METHOD_GET, '/ping', \array_merge([ - 'origin' => 'http://localhost', - ]), [ - 'projectId' => 'fake-project-id', - ]); + 'x-appwrite-project' => 'fake-project-id', + ], $this->getHeaders())); $this->assertEquals(404, $response['headers']['status-code']); $this->assertNotContains('Pong!', $response['body']); diff --git a/tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php b/tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php index 60c96c6e19..4bb9d8711e 100644 --- a/tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php +++ b/tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php @@ -478,6 +478,46 @@ class RealtimeConsoleClientTest extends Scope $client->close(); } + public function testPing() + { + $client = $this->getWebsocket(['console'], [ + 'origin' => 'http://localhost', + 'cookie' => 'a_session_console=' . $this->getRoot()['session'], + ], 'console'); + + $response = json_decode($client->receive(), true); + + $this->assertArrayHasKey('type', $response); + $this->assertEquals('connected', $response['type']); + + fwrite(STDOUT, 'Project ID: ' . $this->getProject()['$id'] . "\n"); + + $pong = $this->client->call(Client::METHOD_GET, '/ping', [ + 'origin' => 'http://localhost', + 'x-appwrite-project' => $this->getProject()['$id'], + ]); + + $this->assertEquals(200, $pong['headers']['status-code']); + $this->assertEquals('Pong!', $pong['body']); + + $response = json_decode($client->receive(), true); + + $this->assertArrayHasKey('type', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertCount(1, $response['data']['channels']); + $this->assertContains('console', $response['data']['channels']); + $this->assertContains("projects.{$this->getProject()['$id']}", $response['data']['channels']); + $this->assertContains("projects.{$this->getProject()['$id']}.ping", $response['data']['events']); + $this->assertNotEmpty($response['data']['payload']); + $this->assertArrayHasKey('pingCount', $response['data']['payload']); + $this->assertArrayHasKey('pingedAt', $response['data']['payload']); + $this->assertEquals(1, $response['data']['payload']['pingCount']); + + $client->close(); + } + public function testCreateDeployment() { $response1 = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ From e0bd500aa2f3ea2c07b82da0bead285a0f1c645c Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Mon, 7 Oct 2024 15:58:34 +0100 Subject: [PATCH 3/3] fix: realtime event --- app/controllers/general.php | 11 ++++------- src/Appwrite/Messaging/Adapter/Realtime.php | 6 ++++++ tests/e2e/General/PingTest.php | 3 +++ .../Services/Realtime/RealtimeConsoleClientTest.php | 4 +--- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/app/controllers/general.php b/app/controllers/general.php index 227745b028..bd49872436 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -30,7 +30,6 @@ use Utopia\Database\Document; use Utopia\Database\Helpers\ID; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; -use Utopia\Database\Validator\UID; use Utopia\Domains\Domain; use Utopia\DSN\DSN; use Utopia\Locale\Locale; @@ -1053,14 +1052,13 @@ include_once __DIR__ . '/shared/api/auth.php'; App::get('/v1/ping') ->groups(['api', 'general']) ->desc('Test the connection between the Appwrite and the SDK.') - ->label('scope', 'public') + ->label('scope', 'global') ->label('event', 'projects.[projectId].ping') - ->param('projectId', '', new UID(), 'Project unique ID.') ->inject('response') - ->inject('console') + ->inject('project') ->inject('dbForConsole') ->inject('queueForEvents') - ->action(function (string $projectId, Response $response, Document $project, Database $dbForConsole, Event $queueForEvents) { + ->action(function (Response $response, Document $project, Database $dbForConsole, Event $queueForEvents) { if ($project->isEmpty()) { throw new AppwriteException(AppwriteException::PROJECT_NOT_FOUND); } @@ -1077,8 +1075,7 @@ App::get('/v1/ping') }); $queueForEvents - ->setProject($project) - ->setParam('projectId', $projectId) + ->setParam('projectId', $project->getId()) ->setPayload($response->output($project, Response::MODEL_PROJECT)); $response->text('Pong!'); diff --git a/src/Appwrite/Messaging/Adapter/Realtime.php b/src/Appwrite/Messaging/Adapter/Realtime.php index d0d4a7c725..c437d4d487 100644 --- a/src/Appwrite/Messaging/Adapter/Realtime.php +++ b/src/Appwrite/Messaging/Adapter/Realtime.php @@ -270,6 +270,12 @@ class Realtime extends Adapter $projectId = 'console'; $roles = [Role::team($project->getAttribute('teamId'))->toString()]; break; + case 'projects': + $channels[] = 'console'; + $channels[] = 'projects.' . $parts[1]; + $projectId = 'console'; + $roles = [Role::team($project->getAttribute('teamId'))->toString()]; + break; case 'teams': if ($parts[2] === 'memberships') { $permissionsChanged = $parts[4] ?? false; diff --git a/tests/e2e/General/PingTest.php b/tests/e2e/General/PingTest.php index 96db658cc3..e41bac6736 100644 --- a/tests/e2e/General/PingTest.php +++ b/tests/e2e/General/PingTest.php @@ -38,6 +38,9 @@ class PingTest extends Scope 'x-appwrite-key' => $this->getProject()['apiKey'], ]); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('Pong!', $response['body']); + /** * Test for FAILURE */ diff --git a/tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php b/tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php index 4bb9d8711e..0155d251f2 100644 --- a/tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php +++ b/tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php @@ -490,8 +490,6 @@ class RealtimeConsoleClientTest extends Scope $this->assertArrayHasKey('type', $response); $this->assertEquals('connected', $response['type']); - fwrite(STDOUT, 'Project ID: ' . $this->getProject()['$id'] . "\n"); - $pong = $this->client->call(Client::METHOD_GET, '/ping', [ 'origin' => 'http://localhost', 'x-appwrite-project' => $this->getProject()['$id'], @@ -506,7 +504,7 @@ class RealtimeConsoleClientTest extends Scope $this->assertEquals('event', $response['type']); $this->assertNotEmpty($response['data']); $this->assertArrayHasKey('timestamp', $response['data']); - $this->assertCount(1, $response['data']['channels']); + $this->assertCount(2, $response['data']['channels']); $this->assertContains('console', $response['data']['channels']); $this->assertContains("projects.{$this->getProject()['$id']}", $response['data']['channels']); $this->assertContains("projects.{$this->getProject()['$id']}.ping", $response['data']['events']);