From ab5ef9776709448243c145ea7ce087be26110c52 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 25 Sep 2024 10:39:11 +0100 Subject: [PATCH 01/28] fix: oauth trigger create user event --- app/controllers/api/account.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 737bd3e09d..4dd89dbde3 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1498,6 +1498,12 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') 'providerType' => MESSAGE_TYPE_EMAIL, 'identifier' => $email, ])); + + $queueForEvents + ->setEvent('users.[userId].create') + ->setParam('userId', $user->getId()) + ->trigger(); + } catch (Duplicate) { $failureRedirect(Exception::USER_ALREADY_EXISTS); } From 3ba7d7c7deda23e49ef6d4d7f5b8174c6b3bb710 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 25 Sep 2024 10:48:39 +0100 Subject: [PATCH 02/28] fix: add payload --- app/controllers/api/account.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 4dd89dbde3..9222ef5546 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1502,6 +1502,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $queueForEvents ->setEvent('users.[userId].create') ->setParam('userId', $user->getId()) + ->setPayload($response->output($user, Response::MODEL_ACCOUNT)) ->trigger(); } catch (Duplicate) { From 5afa8c6158131f7e799688cbe8a3fa2b1f2c0ffe Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 29 Oct 2024 10:51:40 +0000 Subject: [PATCH 03/28] feat: usage db listener --- app/controllers/api/account.php | 7 ------- app/controllers/shared/api.php | 18 ++++++++++++++---- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 10f44e94ea..92d9123840 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -441,7 +441,6 @@ App::get('/v1/account') App::delete('/v1/account') ->desc('Delete account') ->groups(['api', 'account']) - ->label('event', 'users.[userId].delete') ->label('scope', 'account') ->label('audits.event', 'user.delete') ->label('audits.resource', 'user/{response.$id}') @@ -1499,12 +1498,6 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') 'identifier' => $email, ])); - $queueForEvents - ->setEvent('users.[userId].create') - ->setParam('userId', $user->getId()) - ->setPayload($response->output($user, Response::MODEL_ACCOUNT)) - ->trigger(); - } catch (Duplicate) { $failureRedirect(Exception::USER_ALREADY_EXISTS); } diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 6d87940ff7..ceb3ee2f18 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -57,8 +57,17 @@ $parseLabel = function (string $label, array $responsePayload, array $requestPar return $label; }; -$databaseListener = function (string $event, Document $document, Document $project, Usage $queueForUsage, Database $dbForProject) { +$eventDatabaseListener = function (string $event, Document $document, EventDatabase $queueForEvents, Response $response) { + if ($document->getCollection() === 'users' && $event === Database::EVENT_DOCUMENT_CREATE) { + $queueForEvents + ->setEvent('users.[userId].create') + ->setParam('userId', $document->getId()) + ->setPayload($response->output($document, Response::MODEL_USER)) + ->trigger(); + } +}; +$usageDatabaseListener = function (string $event, Document $document, Usage $queueForUsage) { $value = 1; if ($event === Database::EVENT_DOCUMENT_DELETE) { $value = -1; @@ -357,7 +366,7 @@ App::init() ->inject('queueForUsage') ->inject('dbForProject') ->inject('mode') - ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Usage $queueForUsage, Database $dbForProject, string $mode) use ($databaseListener) { + ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Usage $queueForUsage, Database $dbForProject, string $mode) use ($usageDatabaseListener, $eventDatabaseListener) { $route = $utopia->getRoute(); @@ -452,8 +461,9 @@ App::init() $queueForMessaging->setProject($project); $dbForProject - ->on(Database::EVENT_DOCUMENT_CREATE, 'calculate-usage', fn ($event, $document) => $databaseListener($event, $document, $project, $queueForUsage, $dbForProject)) - ->on(Database::EVENT_DOCUMENT_DELETE, 'calculate-usage', fn ($event, $document) => $databaseListener($event, $document, $project, $queueForUsage, $dbForProject)); + ->on(Database::EVENT_DOCUMENT_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForUsage)) + ->on(Database::EVENT_DOCUMENT_DELETE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForUsage)) + ->on(Database::EVENT_DOCUMENT_CREATE, 'trigger-events', fn ($event, $document) => $eventDatabaseListener($event, $document, $queueForEvents, $response)); $useCache = $route->getLabel('cache', false); if ($useCache) { From d16251d261dbf1ee271eeea042f85b9b741b842e Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 29 Oct 2024 10:58:57 +0000 Subject: [PATCH 04/28] fix: remove old create user events --- app/controllers/api/account.php | 6 +--- app/controllers/api/users.php | 53 +++++++++++---------------------- 2 files changed, 18 insertions(+), 41 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 92d9123840..d32836d0ec 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -273,7 +273,6 @@ $createSession = function (string $userId, string $secret, Request $request, Res App::post('/v1/account') ->desc('Create account') ->groups(['api', 'account', 'auth']) - ->label('event', 'users.[userId].create') ->label('scope', 'sessions.write') ->label('auth.type', 'emailPassword') ->label('audits.event', 'user.create') @@ -296,9 +295,8 @@ App::post('/v1/account') ->inject('user') ->inject('project') ->inject('dbForProject') - ->inject('queueForEvents') ->inject('hooks') - ->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) { + ->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Hooks $hooks) { $email = \strtolower($email); if ('console' === $project->getId()) { @@ -408,8 +406,6 @@ App::post('/v1/account') Authorization::setRole(Role::user($user->getId())->toString()); Authorization::setRole(Role::users()->toString()); - $queueForEvents->setParam('userId', $user->getId()); - $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($user, Response::MODEL_ACCOUNT); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 571df4fdb2..42d0875720 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -50,7 +50,7 @@ use Utopia\Validator\Text; use Utopia\Validator\WhiteList; /** TODO: Remove function when we move to using utopia/platform */ -function createUser(string $hash, mixed $hashOptions, string $userId, ?string $email, ?string $password, ?string $phone, string $name, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks): Document +function createUser(string $hash, mixed $hashOptions, string $userId, ?string $email, ?string $password, ?string $phone, string $name, Document $project, Database $dbForProject, Hooks $hooks): Document { $plaintextPassword = $password; $hashOptionsObject = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array @@ -175,15 +175,12 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e throw new Exception(Exception::USER_ALREADY_EXISTS); } - $queueForEvents->setParam('userId', $user->getId()); - return $user; } App::post('/v1/users') ->desc('Create user') ->groups(['api', 'users']) - ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -202,10 +199,9 @@ App::post('/v1/users') ->inject('response') ->inject('project') ->inject('dbForProject') - ->inject('queueForEvents') ->inject('hooks') - ->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) { - $user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $project, $dbForProject, $queueForEvents, $hooks); + ->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { + $user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($user, Response::MODEL_USER); @@ -214,7 +210,6 @@ App::post('/v1/users') App::post('/v1/users/bcrypt') ->desc('Create user with bcrypt password') ->groups(['api', 'users']) - ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -232,10 +227,9 @@ App::post('/v1/users/bcrypt') ->inject('response') ->inject('project') ->inject('dbForProject') - ->inject('queueForEvents') ->inject('hooks') - ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) { - $user = createUser('bcrypt', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks); + ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { + $user = createUser('bcrypt', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -245,7 +239,6 @@ App::post('/v1/users/bcrypt') App::post('/v1/users/md5') ->desc('Create user with MD5 password') ->groups(['api', 'users']) - ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -263,10 +256,9 @@ App::post('/v1/users/md5') ->inject('response') ->inject('project') ->inject('dbForProject') - ->inject('queueForEvents') ->inject('hooks') - ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) { - $user = createUser('md5', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks); + ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { + $user = createUser('md5', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -276,7 +268,6 @@ App::post('/v1/users/md5') App::post('/v1/users/argon2') ->desc('Create user with Argon2 password') ->groups(['api', 'users']) - ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -294,10 +285,9 @@ App::post('/v1/users/argon2') ->inject('response') ->inject('project') ->inject('dbForProject') - ->inject('queueForEvents') ->inject('hooks') - ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) { - $user = createUser('argon2', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks); + ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { + $user = createUser('argon2', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -307,7 +297,6 @@ App::post('/v1/users/argon2') App::post('/v1/users/sha') ->desc('Create user with SHA password') ->groups(['api', 'users']) - ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -326,16 +315,15 @@ App::post('/v1/users/sha') ->inject('response') ->inject('project') ->inject('dbForProject') - ->inject('queueForEvents') ->inject('hooks') - ->action(function (string $userId, string $email, string $password, string $passwordVersion, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) { + ->action(function (string $userId, string $email, string $password, string $passwordVersion, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { $options = '{}'; if (!empty($passwordVersion)) { $options = '{"version":"' . $passwordVersion . '"}'; } - $user = createUser('sha', $options, $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks); + $user = createUser('sha', $options, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -345,7 +333,6 @@ App::post('/v1/users/sha') App::post('/v1/users/phpass') ->desc('Create user with PHPass password') ->groups(['api', 'users']) - ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -363,10 +350,9 @@ App::post('/v1/users/phpass') ->inject('response') ->inject('project') ->inject('dbForProject') - ->inject('queueForEvents') ->inject('hooks') - ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) { - $user = createUser('phpass', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks); + ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { + $user = createUser('phpass', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -376,7 +362,6 @@ App::post('/v1/users/phpass') App::post('/v1/users/scrypt') ->desc('Create user with Scrypt password') ->groups(['api', 'users']) - ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -399,9 +384,8 @@ App::post('/v1/users/scrypt') ->inject('response') ->inject('project') ->inject('dbForProject') - ->inject('queueForEvents') ->inject('hooks') - ->action(function (string $userId, string $email, string $password, string $passwordSalt, int $passwordCpu, int $passwordMemory, int $passwordParallel, int $passwordLength, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) { + ->action(function (string $userId, string $email, string $password, string $passwordSalt, int $passwordCpu, int $passwordMemory, int $passwordParallel, int $passwordLength, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { $options = [ 'salt' => $passwordSalt, 'costCpu' => $passwordCpu, @@ -410,7 +394,7 @@ App::post('/v1/users/scrypt') 'length' => $passwordLength ]; - $user = createUser('scrypt', \json_encode($options), $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks); + $user = createUser('scrypt', \json_encode($options), $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -420,7 +404,6 @@ App::post('/v1/users/scrypt') App::post('/v1/users/scrypt-modified') ->desc('Create user with Scrypt modified password') ->groups(['api', 'users']) - ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -440,11 +423,9 @@ App::post('/v1/users/scrypt-modified') ->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true) ->inject('response') ->inject('project') - ->inject('dbForProject') - ->inject('queueForEvents') ->inject('hooks') - ->action(function (string $userId, string $email, string $password, string $passwordSalt, string $passwordSaltSeparator, string $passwordSignerKey, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) { - $user = createUser('scryptMod', '{"signerKey":"' . $passwordSignerKey . '","saltSeparator":"' . $passwordSaltSeparator . '","salt":"' . $passwordSalt . '"}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks); + ->action(function (string $userId, string $email, string $password, string $passwordSalt, string $passwordSaltSeparator, string $passwordSignerKey, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { + $user = createUser('scryptMod', '{"signerKey":"' . $passwordSignerKey . '","saltSeparator":"' . $passwordSaltSeparator . '","salt":"' . $passwordSalt . '"}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) From 3020c0beea46700cc3d280ab73eb6805f8c5cd0c Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 29 Oct 2024 11:01:29 +0000 Subject: [PATCH 05/28] fix: missing dep --- app/controllers/api/users.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 42d0875720..3beb1526b0 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -423,6 +423,7 @@ App::post('/v1/users/scrypt-modified') ->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true) ->inject('response') ->inject('project') + ->inject('dbForProject') ->inject('hooks') ->action(function (string $userId, string $email, string $password, string $passwordSalt, string $passwordSaltSeparator, string $passwordSignerKey, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { $user = createUser('scryptMod', '{"signerKey":"' . $passwordSignerKey . '","saltSeparator":"' . $passwordSaltSeparator . '","salt":"' . $passwordSalt . '"}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); From 13d1376e282fb592ad4f598d5bb42b288825e01b Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 29 Oct 2024 11:20:52 +0000 Subject: [PATCH 06/28] fix: type --- app/controllers/shared/api.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index ceb3ee2f18..a0151ed9ed 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -57,7 +57,7 @@ $parseLabel = function (string $label, array $responsePayload, array $requestPar return $label; }; -$eventDatabaseListener = function (string $event, Document $document, EventDatabase $queueForEvents, Response $response) { +$eventDatabaseListener = function (string $event, Document $document, Event $queueForEvents, Response $response) { if ($document->getCollection() === 'users' && $event === Database::EVENT_DOCUMENT_CREATE) { $queueForEvents ->setEvent('users.[userId].create') From 9d4fd4170158dbd16715f489a48f12220d6c2f51 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 29 Oct 2024 11:56:41 +0000 Subject: [PATCH 07/28] fix: webhook test --- tests/e2e/Services/Webhooks/WebhooksBase.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/e2e/Services/Webhooks/WebhooksBase.php b/tests/e2e/Services/Webhooks/WebhooksBase.php index 6be3e16c1f..2ef41003ee 100644 --- a/tests/e2e/Services/Webhooks/WebhooksBase.php +++ b/tests/e2e/Services/Webhooks/WebhooksBase.php @@ -901,6 +901,17 @@ trait WebhooksBase $teamId = $data['teamId'] ?? ''; $email = uniqid() . 'friend@localhost.test'; + // Create user to ensure team event is triggered after user event + $user = $this->client->call(Client::METHOD_POST, '/account', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'userId' => ID::unique(), + 'email' => $email, + 'password' => 'password', + 'name' => 'Friend User', + ]); + /** * Test for SUCCESS */ @@ -909,7 +920,6 @@ trait WebhooksBase 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'email' => $email, - 'name' => 'Friend User', 'roles' => ['admin', 'editor'], 'url' => 'http://localhost:5000/join-us#title' ]); From 26c53bcf9cda31f1c2385b49c2c9d4d5011a1b92 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 30 Oct 2024 18:01:12 +0000 Subject: [PATCH 08/28] feat: clone queue --- app/controllers/shared/api.php | 2 +- src/Appwrite/Event/Event.php | 36 ++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index c5f0f3ac85..c65f6356a3 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -468,7 +468,7 @@ App::init() $dbForProject ->on(Database::EVENT_DOCUMENT_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForUsage)) ->on(Database::EVENT_DOCUMENT_DELETE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForUsage)) - ->on(Database::EVENT_DOCUMENT_CREATE, 'trigger-events', fn ($event, $document) => $eventDatabaseListener($event, $document, $queueForEvents, $response)); + ->on(Database::EVENT_DOCUMENT_CREATE, 'trigger-events', fn ($event, $document) => $eventDatabaseListener($event, $document, clone $queueForEvents, $response)); $useCache = $route->getLabel('cache', false); if ($useCache) { diff --git a/src/Appwrite/Event/Event.php b/src/Appwrite/Event/Event.php index 43eda511df..8e5325545c 100644 --- a/src/Appwrite/Event/Event.php +++ b/src/Appwrite/Event/Event.php @@ -319,6 +319,10 @@ class Event return false; } + if (empty($this->event)) { + return false; + } + $client = new Client($this->queue, $this->connection); return $client->enqueue([ @@ -546,4 +550,36 @@ class Event return $this; } + + /** + * Clone event. + */ + public function __clone() + { + if ($this->project instanceof Document) { + $this->project = clone $this->project; + } + + if ($this->user instanceof Document) { + $this->user = clone $this->user; + } + + foreach ($this->context as $key => $value) { + if ($value instanceof Document) { + $this->context[$key] = clone $value; + } + } + + foreach ($this->params as $key => $value) { + if (is_object($value)) { + $this->params[$key] = clone $value; + } + } + + foreach ($this->payload as $key => $value) { + if (is_object($value)) { + $this->payload[$key] = clone $value; + } + } + } } From 2736f6009a651e63d90795f9de85019d70efe91a Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 30 Oct 2024 18:18:37 +0000 Subject: [PATCH 09/28] feat: dirty solution --- app/controllers/shared/api.php | 59 ++++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index c65f6356a3..e9a404de48 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -57,7 +57,7 @@ $parseLabel = function (string $label, array $responsePayload, array $requestPar return $label; }; -$eventDatabaseListener = function (string $event, Document $document, Event $queueForEvents, Response $response) { +$eventDatabaseListener = function (string $event, Document $document, Event $queueForEvents, Response $response, Func $queueForFunctions, Document $project) { if ($document->getCollection() === 'users' && $event === Database::EVENT_DOCUMENT_CREATE) { $queueForEvents ->setEvent('users.[userId].create') @@ -65,6 +65,58 @@ $eventDatabaseListener = function (string $event, Document $document, Event $que ->setPayload($response->output($document, Response::MODEL_USER)) ->trigger(); } + + // FIXME: This is a temporary solution, this should be moved to the shutdown hook + /** + * Trigger functions. + */ + if (!$queueForEvents->isPaused()) { + $queueForFunctions + ->from($queueForEvents) + ->trigger(); + } + + /** + * Trigger webhooks. + */ + $queueForEvents + ->setClass(Event::WEBHOOK_CLASS_NAME) + ->setQueue(Event::WEBHOOK_QUEUE_NAME) + ->trigger(); + + /** + * Trigger realtime. + */ + if ($project->getId() !== 'console') { + $allEvents = Event::generateEvents($queueForEvents->getEvent(), $queueForEvents->getParams()); + $payload = new Document($queueForEvents->getPayload()); + + $db = $queueForEvents->getContext('database'); + $collection = $queueForEvents->getContext('collection'); + $bucket = $queueForEvents->getContext('bucket'); + + $target = Realtime::fromPayload( + // Pass first, most verbose event pattern + event: $allEvents[0], + payload: $payload, + project: $project, + database: $db, + collection: $collection, + bucket: $bucket, + ); + + Realtime::send( + projectId: $target['projectId'] ?? $project->getId(), + payload: $queueForEvents->getRealtimePayload(), + events: $allEvents, + channels: $target['channels'], + roles: $target['roles'], + options: [ + 'permissionsChanged' => $target['permissionsChanged'], + 'userId' => $queueForEvents->getParam('userId') + ] + ); + } }; $usageDatabaseListener = function (string $event, Document $document, Usage $queueForUsage) { @@ -369,9 +421,10 @@ App::init() ->inject('queueForDatabase') ->inject('queueForBuilds') ->inject('queueForUsage') + ->inject('queueForFunctions') ->inject('dbForProject') ->inject('mode') - ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Usage $queueForUsage, Database $dbForProject, string $mode) use ($usageDatabaseListener, $eventDatabaseListener) { + ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Usage $queueForUsage, Func $queueForFunctions, Database $dbForProject, string $mode) use ($usageDatabaseListener, $eventDatabaseListener) { $route = $utopia->getRoute(); @@ -468,7 +521,7 @@ App::init() $dbForProject ->on(Database::EVENT_DOCUMENT_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForUsage)) ->on(Database::EVENT_DOCUMENT_DELETE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForUsage)) - ->on(Database::EVENT_DOCUMENT_CREATE, 'trigger-events', fn ($event, $document) => $eventDatabaseListener($event, $document, clone $queueForEvents, $response)); + ->on(Database::EVENT_DOCUMENT_CREATE, 'trigger-events', fn ($event, $document) => $eventDatabaseListener($event, $document, clone $queueForEvents, $response, $queueForFunctions, $project)); $useCache = $route->getLabel('cache', false); if ($useCache) { From 51ae3a6475c090715f724d090e309792971f5a42 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 30 Oct 2024 18:31:14 +0000 Subject: [PATCH 10/28] fix: tests --- app/controllers/shared/api.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index e9a404de48..ea2d03ccf2 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -58,6 +58,9 @@ $parseLabel = function (string $label, array $responsePayload, array $requestPar }; $eventDatabaseListener = function (string $event, Document $document, Event $queueForEvents, Response $response, Func $queueForFunctions, Document $project) { + // Set event empty to avoid triggering events by default + $queueForEvents->setEvent(''); + if ($document->getCollection() === 'users' && $event === Database::EVENT_DOCUMENT_CREATE) { $queueForEvents ->setEvent('users.[userId].create') From e87ed0e13a3df31ddec1703811309898dad34e87 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 1 Nov 2024 15:24:29 +0000 Subject: [PATCH 11/28] chore: refactor use `from` over clonse --- app/controllers/shared/api.php | 46 ++++++++++++++++++---------------- src/Appwrite/Event/Event.php | 40 ++++++++++------------------- src/Appwrite/Event/Func.php | 18 ------------- 3 files changed, 37 insertions(+), 67 deletions(-) diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index ea2d03ccf2..392fc85cf5 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -28,6 +28,7 @@ use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Helpers\Role; use Utopia\Database\Validator\Authorization; +use Utopia\Queue\Connection; use Utopia\System\System; use Utopia\Validator\WhiteList; @@ -57,31 +58,28 @@ $parseLabel = function (string $label, array $responsePayload, array $requestPar return $label; }; -$eventDatabaseListener = function (string $event, Document $document, Event $queueForEvents, Response $response, Func $queueForFunctions, Document $project) { - // Set event empty to avoid triggering events by default - $queueForEvents->setEvent(''); - - if ($document->getCollection() === 'users' && $event === Database::EVENT_DOCUMENT_CREATE) { - $queueForEvents - ->setEvent('users.[userId].create') - ->setParam('userId', $document->getId()) - ->setPayload($response->output($document, Response::MODEL_USER)) - ->trigger(); +$eventDatabaseListener = function (Document $document, Event $queueForEvents, Response $response, Func $queueForFunctions, Document $project) { + switch ($document->getCollection()) { + case 'users': + $queueForEvents + ->setEvent('users.[userId].update') + ->setParam('userId', $document->getId()) + ->setPayload($response->output($document, Response::MODEL_USER)) + ->trigger(); + break; } // FIXME: This is a temporary solution, this should be moved to the shutdown hook - /** - * Trigger functions. - */ - if (!$queueForEvents->isPaused()) { - $queueForFunctions - ->from($queueForEvents) - ->trigger(); + if (empty($queueForEvents->getEvent())) { + return; } - /** - * Trigger webhooks. - */ + // Trigger functions + $queueForFunctions + ->from($queueForEvents) + ->trigger(); + + // Trigger webhooks $queueForEvents ->setClass(Event::WEBHOOK_CLASS_NAME) ->setQueue(Event::WEBHOOK_QUEUE_NAME) @@ -417,6 +415,7 @@ App::init() ->inject('response') ->inject('project') ->inject('user') + ->inject('queue') ->inject('queueForEvents') ->inject('queueForMessaging') ->inject('queueForAudits') @@ -427,7 +426,7 @@ App::init() ->inject('queueForFunctions') ->inject('dbForProject') ->inject('mode') - ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Usage $queueForUsage, Func $queueForFunctions, Database $dbForProject, string $mode) use ($usageDatabaseListener, $eventDatabaseListener) { + ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Connection $queue, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Usage $queueForUsage, Func $queueForFunctions, Database $dbForProject, string $mode) use ($usageDatabaseListener, $eventDatabaseListener) { $route = $utopia->getRoute(); @@ -521,10 +520,13 @@ App::init() $queueForBuilds->setProject($project); $queueForMessaging->setProject($project); + $queueForEventsClone = new Event($queue); + $queueForEventsClone->from($queueForEvents); + $dbForProject ->on(Database::EVENT_DOCUMENT_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForUsage)) ->on(Database::EVENT_DOCUMENT_DELETE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForUsage)) - ->on(Database::EVENT_DOCUMENT_CREATE, 'trigger-events', fn ($event, $document) => $eventDatabaseListener($event, $document, clone $queueForEvents, $response, $queueForFunctions, $project)); + ->on(Database::EVENT_DOCUMENT_CREATE, 'trigger-events', fn ($event, $document) => $eventDatabaseListener($document, clone $queueForEvents, $response, $queueForFunctions, $project)); $useCache = $route->getLabel('cache', false); if ($useCache) { diff --git a/src/Appwrite/Event/Event.php b/src/Appwrite/Event/Event.php index 8e5325545c..14cb1ef4b7 100644 --- a/src/Appwrite/Event/Event.php +++ b/src/Appwrite/Event/Event.php @@ -552,34 +552,20 @@ class Event } /** - * Clone event. + * Generate a function event from a base event + * + * @param Event $event + * + * @return self + * */ - public function __clone() + public function from(Event $event): self { - if ($this->project instanceof Document) { - $this->project = clone $this->project; - } - - if ($this->user instanceof Document) { - $this->user = clone $this->user; - } - - foreach ($this->context as $key => $value) { - if ($value instanceof Document) { - $this->context[$key] = clone $value; - } - } - - foreach ($this->params as $key => $value) { - if (is_object($value)) { - $this->params[$key] = clone $value; - } - } - - foreach ($this->payload as $key => $value) { - if (is_object($value)) { - $this->payload[$key] = clone $value; - } - } + $this->project = $event->getProject(); + $this->user = $event->getUser(); + $this->payload = $event->getPayload(); + $this->event = $event->getEvent(); + $this->params = $event->getParams(); + return $this; } } diff --git a/src/Appwrite/Event/Func.php b/src/Appwrite/Event/Func.php index 4dad5802f7..0ad639a9f5 100644 --- a/src/Appwrite/Event/Func.php +++ b/src/Appwrite/Event/Func.php @@ -238,22 +238,4 @@ class Func extends Event 'method' => $this->method, ]); } - - /** - * Generate a function event from a base event - * - * @param Event $event - * - * @return self - * - */ - public function from(Event $event): self - { - $this->project = $event->getProject(); - $this->user = $event->getUser(); - $this->payload = $event->getPayload(); - $this->event = $event->getEvent(); - $this->params = $event->getParams(); - return $this; - } } From ff95532ccb7369fc182af8f26c35dcb62906d498 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 1 Nov 2024 16:29:34 +0000 Subject: [PATCH 12/28] chore: refactor --- app/controllers/shared/api.php | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 392fc85cf5..043c346587 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -59,21 +59,15 @@ $parseLabel = function (string $label, array $responsePayload, array $requestPar }; $eventDatabaseListener = function (Document $document, Event $queueForEvents, Response $response, Func $queueForFunctions, Document $project) { - switch ($document->getCollection()) { - case 'users': - $queueForEvents - ->setEvent('users.[userId].update') - ->setParam('userId', $document->getId()) - ->setPayload($response->output($document, Response::MODEL_USER)) - ->trigger(); - break; - } - - // FIXME: This is a temporary solution, this should be moved to the shutdown hook - if (empty($queueForEvents->getEvent())) { + if (!$document->getCollection() === 'users') { return; } + $queueForEvents + ->setEvent('users.[userId].update') + ->setParam('userId', $document->getId()) + ->setPayload($response->output($document, Response::MODEL_USER)); + // Trigger functions $queueForFunctions ->from($queueForEvents) @@ -85,9 +79,7 @@ $eventDatabaseListener = function (Document $document, Event $queueForEvents, Re ->setQueue(Event::WEBHOOK_QUEUE_NAME) ->trigger(); - /** - * Trigger realtime. - */ + // Trigger realtime if ($project->getId() !== 'console') { $allEvents = Event::generateEvents($queueForEvents->getEvent(), $queueForEvents->getParams()); $payload = new Document($queueForEvents->getPayload()); From d2deca7f1faeb2549ee2c9067454d7e60b0b1ff2 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Mon, 4 Nov 2024 15:05:54 +0000 Subject: [PATCH 13/28] feat: refactor events --- app/controllers/shared/api.php | 154 +++++++++++--------------------- app/init.php | 8 ++ src/Appwrite/Event/Event.php | 13 --- src/Appwrite/Event/Realtime.php | 74 +++++++++++++++ src/Appwrite/Event/Webhook.php | 17 ++++ 5 files changed, 151 insertions(+), 115 deletions(-) create mode 100644 src/Appwrite/Event/Realtime.php create mode 100644 src/Appwrite/Event/Webhook.php diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 043c346587..b2a037d33a 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -12,6 +12,7 @@ use Appwrite\Event\Event; use Appwrite\Event\Func; use Appwrite\Event\Messaging; use Appwrite\Event\Usage; +use Appwrite\Event\Webhook; use Appwrite\Extend\Exception; use Appwrite\Extend\Exception as AppwriteException; use Appwrite\Messaging\Adapter\Realtime; @@ -58,7 +59,8 @@ $parseLabel = function (string $label, array $responsePayload, array $requestPar return $label; }; -$eventDatabaseListener = function (Document $document, Event $queueForEvents, Response $response, Func $queueForFunctions, Document $project) { +$eventDatabaseListener = function (Document $document, Response $response, Event $queueForEvents, Func $queueForFunctions, Webhook $queueForWebhooks, Realtime $queueForRealtime) use ($triggerEventQueues) { + // For now, we only use user creation events with the database listener. if (!$document->getCollection() === 'users') { return; } @@ -68,48 +70,13 @@ $eventDatabaseListener = function (Document $document, Event $queueForEvents, Re ->setParam('userId', $document->getId()) ->setPayload($response->output($document, Response::MODEL_USER)); - // Trigger functions - $queueForFunctions - ->from($queueForEvents) - ->trigger(); - - // Trigger webhooks - $queueForEvents - ->setClass(Event::WEBHOOK_CLASS_NAME) - ->setQueue(Event::WEBHOOK_QUEUE_NAME) - ->trigger(); - - // Trigger realtime - if ($project->getId() !== 'console') { - $allEvents = Event::generateEvents($queueForEvents->getEvent(), $queueForEvents->getParams()); - $payload = new Document($queueForEvents->getPayload()); - - $db = $queueForEvents->getContext('database'); - $collection = $queueForEvents->getContext('collection'); - $bucket = $queueForEvents->getContext('bucket'); - - $target = Realtime::fromPayload( - // Pass first, most verbose event pattern - event: $allEvents[0], - payload: $payload, - project: $project, - database: $db, - collection: $collection, - bucket: $bucket, - ); - - Realtime::send( - projectId: $target['projectId'] ?? $project->getId(), - payload: $queueForEvents->getRealtimePayload(), - events: $allEvents, - channels: $target['channels'], - roles: $target['roles'], - options: [ - 'permissionsChanged' => $target['permissionsChanged'], - 'userId' => $queueForEvents->getParam('userId') - ] - ); - } + // Trigger functions, webhooks, and realtime events + $triggerEventQueues( + $queueForEvents, + $queueForFunctions, + $queueForWebhooks, + $queueForRealtime + ); }; $usageDatabaseListener = function (string $event, Document $document, Usage $queueForUsage) { @@ -400,6 +367,29 @@ App::init() } }); +$triggerEventQueues = function ($queueForEvents, $queueForFunctions, $queueForWebhooks, $queueForRealtime) { + if (empty($queueForEvents->getEvent())) { + return; + } + + $queueForFunctions + ->from($queueForEvents) + ->trigger(); + + $queueForWebhooks + ->from($queueForEvents) + ->trigger(); + + // Console can listen to events from other projects, but it should not trigger events for them. + if ($queueForEvents->getProject()->getId() === 'console') { + return; + } + + $queueForRealtime + ->from($queueForEvents) + ->trigger(); +}; + App::init() ->groups(['api']) ->inject('utopia') @@ -416,9 +406,11 @@ App::init() ->inject('queueForBuilds') ->inject('queueForUsage') ->inject('queueForFunctions') + ->inject('queueForWebhooks') + ->inject('queueForRealtime') ->inject('dbForProject') ->inject('mode') - ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Connection $queue, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Usage $queueForUsage, Func $queueForFunctions, Database $dbForProject, string $mode) use ($usageDatabaseListener, $eventDatabaseListener) { + ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Connection $queue, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Usage $queueForUsage, Func $queueForFunctions, Webhook $queueForWebhooks, Realtime $queueForRealtime, Database $dbForProject, string $mode) use ($usageDatabaseListener, $eventDatabaseListener) { $route = $utopia->getRoute(); @@ -512,13 +504,14 @@ App::init() $queueForBuilds->setProject($project); $queueForMessaging->setProject($project); + // Clone the queueForEvents, to prevent events triggered by the database listener + // from overwriting the events that are supposed to be triggered in the shutdown hook. $queueForEventsClone = new Event($queue); - $queueForEventsClone->from($queueForEvents); $dbForProject ->on(Database::EVENT_DOCUMENT_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForUsage)) ->on(Database::EVENT_DOCUMENT_DELETE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForUsage)) - ->on(Database::EVENT_DOCUMENT_CREATE, 'trigger-events', fn ($event, $document) => $eventDatabaseListener($document, clone $queueForEvents, $response, $queueForFunctions, $project)); + ->on(Database::EVENT_DOCUMENT_CREATE, 'create-trigger-events', fn ($event, $document) => $eventDatabaseListener($document, $response, $queueForEventsClone->from($queueForEvents), $queueForFunctions, $queueForWebhooks, $queueForRealtime)); $useCache = $route->getLabel('cache', false); if ($useCache) { @@ -651,70 +644,27 @@ App::shutdown() ->inject('queueForDatabase') ->inject('queueForBuilds') ->inject('queueForMessaging') - ->inject('dbForProject') ->inject('queueForFunctions') + ->inject('queueForWebhooks') + ->inject('queueForRealtime') + ->inject('dbForProject') ->inject('mode') ->inject('dbForConsole') - ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Audit $queueForAudits, Usage $queueForUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Database $dbForProject, Func $queueForFunctions, string $mode, Database $dbForConsole) use ($parseLabel) { + ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Audit $queueForAudits, Usage $queueForUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Func $queueForFunctions, Event $queueForWebhooks, Realtime $queueForRealtime, Database $dbForProject, string $mode, Database $dbForConsole) use ($parseLabel, $triggerEventQueues) { $responsePayload = $response->getPayload(); - if (!empty($queueForEvents->getEvent())) { - if (empty($queueForEvents->getPayload())) { - $queueForEvents->setPayload($responsePayload); - } - - /** - * Trigger functions. - */ - if (!$queueForEvents->isPaused()) { - $queueForFunctions - ->from($queueForEvents) - ->trigger(); - } - /** - * Trigger webhooks. - */ - $queueForEvents - ->setClass(Event::WEBHOOK_CLASS_NAME) - ->setQueue(Event::WEBHOOK_QUEUE_NAME) - ->trigger(); - - /** - * Trigger realtime. - */ - if ($project->getId() !== 'console') { - $allEvents = Event::generateEvents($queueForEvents->getEvent(), $queueForEvents->getParams()); - $payload = new Document($queueForEvents->getPayload()); - - $db = $queueForEvents->getContext('database'); - $collection = $queueForEvents->getContext('collection'); - $bucket = $queueForEvents->getContext('bucket'); - - $target = Realtime::fromPayload( - // Pass first, most verbose event pattern - event: $allEvents[0], - payload: $payload, - project: $project, - database: $db, - collection: $collection, - bucket: $bucket, - ); - - Realtime::send( - projectId: $target['projectId'] ?? $project->getId(), - payload: $queueForEvents->getRealtimePayload(), - events: $allEvents, - channels: $target['channels'], - roles: $target['roles'], - options: [ - 'permissionsChanged' => $target['permissionsChanged'], - 'userId' => $queueForEvents->getParam('userId') - ] - ); - } + if (empty($queueForEvents->getPayload())) { + $queueForEvents->setPayload($responsePayload); } + $triggerEventQueues( + $queueForEvents, + $queueForFunctions, + $queueForWebhooks, + $queueForRealtime + ); + $route = $utopia->getRoute(); $requestParams = $route->getParamsValues(); diff --git a/app/init.php b/app/init.php index e962c58b67..d062e218e9 100644 --- a/app/init.php +++ b/app/init.php @@ -31,7 +31,9 @@ use Appwrite\Event\Func; use Appwrite\Event\Mail; use Appwrite\Event\Messaging; use Appwrite\Event\Migration; +use Appwrite\Event\Realtime; use Appwrite\Event\Usage; +use Appwrite\Event\Webhook; use Appwrite\Extend\Exception; use Appwrite\Functions\Specification; use Appwrite\GraphQL\Promises\Adapter\Swoole; @@ -1134,6 +1136,12 @@ App::setResource('queueForDeletes', function (Connection $queue) { App::setResource('queueForEvents', function (Connection $queue) { return new Event($queue); }, ['queue']); +App::setResource('queueForWebhooks', function (Connection $queue) { + return new Webhook($queue); +}, ['queue']); +App::setResource('queueForRealtime', function () { + return new Realtime(); +}, []); App::setResource('queueForAudits', function (Connection $queue) { return new Audit($queue); }, ['queue']); diff --git a/src/Appwrite/Event/Event.php b/src/Appwrite/Event/Event.php index 14cb1ef4b7..187203b04c 100644 --- a/src/Appwrite/Event/Event.php +++ b/src/Appwrite/Event/Event.php @@ -204,19 +204,6 @@ class Event return $this->payload; } - public function getRealtimePayload(): array - { - $payload = []; - - foreach ($this->payload as $key => $value) { - if (!isset($this->sensitive[$key])) { - $payload[$key] = $value; - } - } - - return $payload; - } - /** * Set context for this event. * diff --git a/src/Appwrite/Event/Realtime.php b/src/Appwrite/Event/Realtime.php new file mode 100644 index 0000000000..394de9a612 --- /dev/null +++ b/src/Appwrite/Event/Realtime.php @@ -0,0 +1,74 @@ +payload as $key => $value) { + if (!isset($this->sensitive[$key])) { + $payload[$key] = $value; + } + } + + return $payload; + } + + /** + * Execute Event. + * + * @return string|bool + * @throws InvalidArgumentException + */ + public function trigger(): string|bool + { + if ($this->paused) { + return false; + } + + if (empty($this->event)) { + return false; + } + + $allEvents = Event::generateEvents($this->getEvent(), $this->getParams()); + $payload = new Document($this->getPayload()); + + $db = $this->getContext('database'); + $collection = $this->getContext('collection'); + $bucket = $this->getContext('bucket'); + + $target = RealtimeAdapter::fromPayload( + // Pass first, most verbose event pattern + event: $allEvents[0], + payload: $payload, + project: $this->getProject(), + database: $db, + collection: $collection, + bucket: $bucket, + ); + + RealtimeAdapter::send( + projectId: $target['projectId'] ?? $this->getProject()->getId(), + payload: $this->getRealtimePayload(), + events: $allEvents, + channels: $target['channels'], + roles: $target['roles'], + options: [ + 'permissionsChanged' => $target['permissionsChanged'], + 'userId' => $this->getParam('userId') + ] + ); + + return true; + } +} diff --git a/src/Appwrite/Event/Webhook.php b/src/Appwrite/Event/Webhook.php new file mode 100644 index 0000000000..125c9a78d5 --- /dev/null +++ b/src/Appwrite/Event/Webhook.php @@ -0,0 +1,17 @@ +setQueue(Event::WEBHOOK_QUEUE_NAME) + ->setClass(Event::WEBHOOK_CLASS_NAME); + } +} From 20d8a702a126b31eaaab18598864632a14bab56c Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Mon, 4 Nov 2024 15:20:43 +0000 Subject: [PATCH 14/28] chore: refactor --- app/controllers/shared/api.php | 81 ++++++++++++++++----------------- src/Appwrite/Event/Event.php | 22 --------- src/Appwrite/Event/Func.php | 4 -- src/Appwrite/Event/Realtime.php | 4 -- 4 files changed, 38 insertions(+), 73 deletions(-) diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index b2a037d33a..56137631e8 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -11,11 +11,11 @@ use Appwrite\Event\Delete; use Appwrite\Event\Event; use Appwrite\Event\Func; use Appwrite\Event\Messaging; +use Appwrite\Event\Realtime; use Appwrite\Event\Usage; use Appwrite\Event\Webhook; use Appwrite\Extend\Exception; use Appwrite\Extend\Exception as AppwriteException; -use Appwrite\Messaging\Adapter\Realtime; use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; use Utopia\Abuse\Abuse; @@ -59,9 +59,9 @@ $parseLabel = function (string $label, array $responsePayload, array $requestPar return $label; }; -$eventDatabaseListener = function (Document $document, Response $response, Event $queueForEvents, Func $queueForFunctions, Webhook $queueForWebhooks, Realtime $queueForRealtime) use ($triggerEventQueues) { - // For now, we only use user creation events with the database listener. - if (!$document->getCollection() === 'users') { +$eventDatabaseListener = function (Document $document, Response $response, Event $queueForEvents, Func $queueForFunctions, Webhook $queueForWebhooks, Realtime $queueForRealtime) { + // Only trigger events for user creation with the database listener. + if ($document->getCollection() !== 'users') { return; } @@ -71,12 +71,21 @@ $eventDatabaseListener = function (Document $document, Response $response, Event ->setPayload($response->output($document, Response::MODEL_USER)); // Trigger functions, webhooks, and realtime events - $triggerEventQueues( - $queueForEvents, - $queueForFunctions, - $queueForWebhooks, - $queueForRealtime - ); + $queueForFunctions + ->from($queueForEvents) + ->trigger(); + + $queueForWebhooks + ->from($queueForEvents) + ->trigger(); + + if ($queueForEvents->getProject()->getId() === 'console') { + return; + } + + $queueForRealtime + ->from($queueForEvents) + ->trigger(); }; $usageDatabaseListener = function (string $event, Document $document, Usage $queueForUsage) { @@ -367,29 +376,6 @@ App::init() } }); -$triggerEventQueues = function ($queueForEvents, $queueForFunctions, $queueForWebhooks, $queueForRealtime) { - if (empty($queueForEvents->getEvent())) { - return; - } - - $queueForFunctions - ->from($queueForEvents) - ->trigger(); - - $queueForWebhooks - ->from($queueForEvents) - ->trigger(); - - // Console can listen to events from other projects, but it should not trigger events for them. - if ($queueForEvents->getProject()->getId() === 'console') { - return; - } - - $queueForRealtime - ->from($queueForEvents) - ->trigger(); -}; - App::init() ->groups(['api']) ->inject('utopia') @@ -650,20 +636,29 @@ App::shutdown() ->inject('dbForProject') ->inject('mode') ->inject('dbForConsole') - ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Audit $queueForAudits, Usage $queueForUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Func $queueForFunctions, Event $queueForWebhooks, Realtime $queueForRealtime, Database $dbForProject, string $mode, Database $dbForConsole) use ($parseLabel, $triggerEventQueues) { + ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Audit $queueForAudits, Usage $queueForUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Func $queueForFunctions, Event $queueForWebhooks, Realtime $queueForRealtime, Database $dbForProject, string $mode, Database $dbForConsole) use ($parseLabel) { $responsePayload = $response->getPayload(); - if (empty($queueForEvents->getPayload())) { - $queueForEvents->setPayload($responsePayload); - } + if (!empty($queueForEvents->getEvent())) { + if (empty($queueForEvents->getPayload())) { + $queueForEvents->setPayload($responsePayload); + } - $triggerEventQueues( - $queueForEvents, - $queueForFunctions, - $queueForWebhooks, - $queueForRealtime - ); + $queueForWebhooks + ->from($queueForEvents) + ->trigger(); + + $queueForFunctions + ->from($queueForEvents) + ->trigger(); + + if ($project->getId() !== 'console') { + $queueForRealtime + ->from($queueForEvents) + ->trigger(); + } + } $route = $utopia->getRoute(); $requestParams = $route->getParamsValues(); diff --git a/src/Appwrite/Event/Event.php b/src/Appwrite/Event/Event.php index 187203b04c..d15ccc39bc 100644 --- a/src/Appwrite/Event/Event.php +++ b/src/Appwrite/Event/Event.php @@ -302,10 +302,6 @@ class Event */ public function trigger(): string|bool { - if ($this->paused) { - return false; - } - if (empty($this->event)) { return false; } @@ -520,24 +516,6 @@ class Event return \array_values($events); } - /** - * Get the value of paused - */ - public function isPaused(): bool - { - return $this->paused; - } - - /** - * Set the value of paused - */ - public function setPaused(bool $paused): self - { - $this->paused = $paused; - - return $this; - } - /** * Generate a function event from a base event * diff --git a/src/Appwrite/Event/Func.php b/src/Appwrite/Event/Func.php index 0ad639a9f5..11a445d8ed 100644 --- a/src/Appwrite/Event/Func.php +++ b/src/Appwrite/Event/Func.php @@ -213,10 +213,6 @@ class Func extends Event */ public function trigger(): string|bool { - if ($this->paused) { - return false; - } - $client = new Client($this->queue, $this->connection); $events = $this->getEvent() ? Event::generateEvents($this->getEvent(), $this->getParams()) : null; diff --git a/src/Appwrite/Event/Realtime.php b/src/Appwrite/Event/Realtime.php index 394de9a612..e158076f9b 100644 --- a/src/Appwrite/Event/Realtime.php +++ b/src/Appwrite/Event/Realtime.php @@ -32,10 +32,6 @@ class Realtime extends Event */ public function trigger(): string|bool { - if ($this->paused) { - return false; - } - if (empty($this->event)) { return false; } From cca0c3ae45864421295b3b48d41998ff36e6680c Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 5 Nov 2024 12:59:11 +0545 Subject: [PATCH 15/28] Make compression param whitelist strict --- app/controllers/api/storage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index dda39a0db0..ed10c84912 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -68,7 +68,7 @@ App::post('/v1/storage/buckets') ->param('enabled', true, new Boolean(true), 'Is bucket enabled? When set to \'disabled\', users cannot access the files in this bucket but Server SDKs with and API key can still access the bucket. No files are lost when this is toggled.', true) ->param('maximumFileSize', fn (array $plan) => empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000, fn (array $plan) => new Range(1, empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(System::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true, ['plan']) ->param('allowedFileExtensions', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Allowed file extensions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' extensions are allowed, each 64 characters long.', true) - ->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD]), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true) + ->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD], true), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true) ->param('encryption', true, new Boolean(true), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' encryption is skipped even if it\'s enabled', true) ->param('antivirus', true, new Boolean(true), 'Is virus scanning enabled? For file size above ' . Storage::human(APP_LIMIT_ANTIVIRUS, 0) . ' AntiVirus scanning is skipped even if it\'s enabled', true) ->inject('response') From 028c5366c20b9a88699f88cdf72d1f4627dd9d1e Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 5 Nov 2024 08:12:58 +0000 Subject: [PATCH 16/28] make compression nullable and assign default in action - also update the `update bucket` endpoint --- app/controllers/api/storage.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index ed10c84912..911481a1c0 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -74,13 +74,13 @@ App::post('/v1/storage/buckets') ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') - ->action(function (string $bucketId, string $name, ?array $permissions, bool $fileSecurity, bool $enabled, int $maximumFileSize, array $allowedFileExtensions, string $compression, bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $queueForEvents) { + ->action(function (string $bucketId, string $name, ?array $permissions, bool $fileSecurity, bool $enabled, int $maximumFileSize, array $allowedFileExtensions, ?string $compression, bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $queueForEvents) { $bucketId = $bucketId === 'unique()' ? ID::unique() : $bucketId; // Map aggregate permissions into the multiple permissions they represent. $permissions = Permission::aggregate($permissions); - + $compression ??= Compression::NONE; try { $files = (Config::getParam('collections', [])['buckets'] ?? [])['files'] ?? []; if (empty($files)) { @@ -254,13 +254,13 @@ App::put('/v1/storage/buckets/:bucketId') ->param('enabled', true, new Boolean(true), 'Is bucket enabled? When set to \'disabled\', users cannot access the files in this bucket but Server SDKs with and API key can still access the bucket. No files are lost when this is toggled.', true) ->param('maximumFileSize', fn (array $plan) => empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000, fn (array $plan) => new Range(1, empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(System::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true, ['plan']) ->param('allowedFileExtensions', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Allowed file extensions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' extensions are allowed, each 64 characters long.', true) - ->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD]), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true) + ->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD], true), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true) ->param('encryption', true, new Boolean(true), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' encryption is skipped even if it\'s enabled', true) ->param('antivirus', true, new Boolean(true), 'Is virus scanning enabled? For file size above ' . Storage::human(APP_LIMIT_ANTIVIRUS, 0) . ' AntiVirus scanning is skipped even if it\'s enabled', true) ->inject('response') ->inject('dbForProject') ->inject('queueForEvents') - ->action(function (string $bucketId, string $name, ?array $permissions, bool $fileSecurity, bool $enabled, ?int $maximumFileSize, array $allowedFileExtensions, string $compression, bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $queueForEvents) { + ->action(function (string $bucketId, string $name, ?array $permissions, bool $fileSecurity, bool $enabled, ?int $maximumFileSize, array $allowedFileExtensions, ?string $compression, bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $queueForEvents) { $bucket = $dbForProject->getDocument('buckets', $bucketId); if ($bucket->isEmpty()) { @@ -273,6 +273,7 @@ App::put('/v1/storage/buckets/:bucketId') $enabled ??= $bucket->getAttribute('enabled', true); $encryption ??= $bucket->getAttribute('encryption', true); $antivirus ??= $bucket->getAttribute('antivirus', true); + $compression ??= $bucket->getAttribute('compression', Compression::NONE); // Map aggregate permissions into the multiple permissions they represent. $permissions = Permission::aggregate($permissions); From e9a0f47711d1a2eddbc7a2bcab1c8b9139449b27 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 5 Nov 2024 09:55:56 +0100 Subject: [PATCH 17/28] fix: event test --- src/Appwrite/Event/Event.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Appwrite/Event/Event.php b/src/Appwrite/Event/Event.php index d15ccc39bc..325aca8c62 100644 --- a/src/Appwrite/Event/Event.php +++ b/src/Appwrite/Event/Event.php @@ -302,10 +302,6 @@ class Event */ public function trigger(): string|bool { - if (empty($this->event)) { - return false; - } - $client = new Client($this->queue, $this->connection); return $client->enqueue([ From 540e997fdbce9cdc7b874ab43e1af4c43ed135a6 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 5 Nov 2024 23:15:08 +1300 Subject: [PATCH 18/28] Update database --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index f122149ec2..5a2df912fd 100644 --- a/composer.json +++ b/composer.json @@ -51,7 +51,7 @@ "utopia-php/cache": "0.10.*", "utopia-php/cli": "0.15.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "0.53.12", + "utopia-php/database": "0.53.13", "utopia-php/domains": "0.5.*", "utopia-php/dsn": "0.2.1", "utopia-php/framework": "0.33.*", diff --git a/composer.lock b/composer.lock index 0650710611..452aefd26f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "102bab0efa76adc92312b9c5a7f488e3", + "content-hash": "f6eb364e8504ebc2f6c9fe38d75f7e86", "packages": [ { "name": "adhocore/jwt", @@ -1724,16 +1724,16 @@ }, { "name": "utopia-php/database", - "version": "0.53.12", + "version": "0.53.13", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "4068d0c5c503ec4562f8c0aa9cfd095889418b83" + "reference": "a7e5de257e36e1b804d35b307865dd4036baa33e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/4068d0c5c503ec4562f8c0aa9cfd095889418b83", - "reference": "4068d0c5c503ec4562f8c0aa9cfd095889418b83", + "url": "https://api.github.com/repos/utopia-php/database/zipball/a7e5de257e36e1b804d35b307865dd4036baa33e", + "reference": "a7e5de257e36e1b804d35b307865dd4036baa33e", "shasum": "" }, "require": { @@ -1774,9 +1774,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.53.12" + "source": "https://github.com/utopia-php/database/tree/0.53.13" }, - "time": "2024-11-04T07:51:44+00:00" + "time": "2024-11-05T10:08:05+00:00" }, { "name": "utopia-php/domains", From 2dea9b1f51a1cfde8c0437b2eaf8b53b165164fe Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 5 Nov 2024 14:35:35 +0100 Subject: [PATCH 19/28] chore: clone --- app/controllers/shared/api.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 56137631e8..a07bc06a91 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -490,14 +490,24 @@ App::init() $queueForBuilds->setProject($project); $queueForMessaging->setProject($project); - // Clone the queueForEvents, to prevent events triggered by the database listener + // Clone the queues, to prevent events triggered by the database listener // from overwriting the events that are supposed to be triggered in the shutdown hook. $queueForEventsClone = new Event($queue); + $queueForFunctionsClone = new Func($queue); + $queueForWebhooksClone = new Webhook($queue); + $queueForRealtimeClone = new Realtime($queue); $dbForProject ->on(Database::EVENT_DOCUMENT_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForUsage)) ->on(Database::EVENT_DOCUMENT_DELETE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForUsage)) - ->on(Database::EVENT_DOCUMENT_CREATE, 'create-trigger-events', fn ($event, $document) => $eventDatabaseListener($document, $response, $queueForEventsClone->from($queueForEvents), $queueForFunctions, $queueForWebhooks, $queueForRealtime)); + ->on(Database::EVENT_DOCUMENT_CREATE, 'create-trigger-events', fn ($event, $document) => $eventDatabaseListener( + $document, + $response, + $queueForEventsClone->from($queueForEvents), + $queueForFunctionsClone->from($queueForEvents), + $queueForWebhooksClone->from($queueForEvents), + $queueForRealtimeClone->from($queueForEvents) + )); $useCache = $route->getLabel('cache', false); if ($useCache) { From ab7e45429222e36150a0c74ee234badcf554db1f Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 5 Nov 2024 15:17:41 +0100 Subject: [PATCH 20/28] fix: event name --- app/controllers/shared/api.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index a07bc06a91..8835247574 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -66,7 +66,7 @@ $eventDatabaseListener = function (Document $document, Response $response, Event } $queueForEvents - ->setEvent('users.[userId].update') + ->setEvent('users.[userId].create') ->setParam('userId', $document->getId()) ->setPayload($response->output($document, Response::MODEL_USER)); From 229fe9ecd5b37b1fc5db664c3b5d7f311da81b53 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 5 Nov 2024 15:50:32 +0100 Subject: [PATCH 21/28] fix: tests --- app/controllers/shared/api.php | 17 +++++++---------- src/Appwrite/Event/Event.php | 1 + 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 8835247574..f5921bf6e8 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -391,12 +391,9 @@ App::init() ->inject('queueForDatabase') ->inject('queueForBuilds') ->inject('queueForUsage') - ->inject('queueForFunctions') - ->inject('queueForWebhooks') - ->inject('queueForRealtime') ->inject('dbForProject') ->inject('mode') - ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Connection $queue, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Usage $queueForUsage, Func $queueForFunctions, Webhook $queueForWebhooks, Realtime $queueForRealtime, Database $dbForProject, string $mode) use ($usageDatabaseListener, $eventDatabaseListener) { + ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Connection $queue, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Usage $queueForUsage, Database $dbForProject, string $mode) use ($usageDatabaseListener, $eventDatabaseListener) { $route = $utopia->getRoute(); @@ -493,9 +490,9 @@ App::init() // Clone the queues, to prevent events triggered by the database listener // from overwriting the events that are supposed to be triggered in the shutdown hook. $queueForEventsClone = new Event($queue); - $queueForFunctionsClone = new Func($queue); - $queueForWebhooksClone = new Webhook($queue); - $queueForRealtimeClone = new Realtime($queue); + $queueForFunctions = new Func($queue); + $queueForWebhooks = new Webhook($queue); + $queueForRealtime = new Realtime(); $dbForProject ->on(Database::EVENT_DOCUMENT_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForUsage)) @@ -504,9 +501,9 @@ App::init() $document, $response, $queueForEventsClone->from($queueForEvents), - $queueForFunctionsClone->from($queueForEvents), - $queueForWebhooksClone->from($queueForEvents), - $queueForRealtimeClone->from($queueForEvents) + $queueForFunctions->from($queueForEvents), + $queueForWebhooks->from($queueForEvents), + $queueForRealtime->from($queueForEvents) )); $useCache = $route->getLabel('cache', false); diff --git a/src/Appwrite/Event/Event.php b/src/Appwrite/Event/Event.php index 325aca8c62..3f166ad7a4 100644 --- a/src/Appwrite/Event/Event.php +++ b/src/Appwrite/Event/Event.php @@ -527,6 +527,7 @@ class Event $this->payload = $event->getPayload(); $this->event = $event->getEvent(); $this->params = $event->getParams(); + $this->context = $event->context; return $this; } } From c6b297dc82064f785b6cb32986d578b01e4f1c9c Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 6 Nov 2024 15:05:04 +1300 Subject: [PATCH 22/28] Update database for transaction counter fixes with retries --- composer.json | 4 +-- composer.lock | 72 +++++++++++++++++++++++++-------------------------- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/composer.json b/composer.json index 5a2df912fd..dbd1575919 100644 --- a/composer.json +++ b/composer.json @@ -48,10 +48,10 @@ "utopia-php/abuse": "0.43.0", "utopia-php/analytics": "0.10.*", "utopia-php/audit": "0.43.0", - "utopia-php/cache": "0.10.*", + "utopia-php/cache": "0.11.*", "utopia-php/cli": "0.15.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "0.53.13", + "utopia-php/database": "0.53.15", "utopia-php/domains": "0.5.*", "utopia-php/dsn": "0.2.1", "utopia-php/framework": "0.33.*", diff --git a/composer.lock b/composer.lock index 452aefd26f..995afcd426 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f6eb364e8504ebc2f6c9fe38d75f7e86", + "content-hash": "fb924a3640fb2c2e6e273718415f8205", "packages": [ { "name": "adhocore/jwt", @@ -1574,16 +1574,16 @@ }, { "name": "utopia-php/cache", - "version": "0.10.2", + "version": "0.11.0", "source": { "type": "git", "url": "https://github.com/utopia-php/cache.git", - "reference": "b22c6eb6d308de246b023efd0fc9758aee8b8247" + "reference": "8ebcab5aac7606331cef69b0081f6c9eff2e58bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cache/zipball/b22c6eb6d308de246b023efd0fc9758aee8b8247", - "reference": "b22c6eb6d308de246b023efd0fc9758aee8b8247", + "url": "https://api.github.com/repos/utopia-php/cache/zipball/8ebcab5aac7606331cef69b0081f6c9eff2e58bc", + "reference": "8ebcab5aac7606331cef69b0081f6c9eff2e58bc", "shasum": "" }, "require": { @@ -1594,7 +1594,7 @@ }, "require-dev": { "laravel/pint": "1.2.*", - "phpstan/phpstan": "1.9.x-dev", + "phpstan/phpstan": "^1.12", "phpunit/phpunit": "^9.3", "vimeo/psalm": "4.13.1" }, @@ -1618,9 +1618,9 @@ ], "support": { "issues": "https://github.com/utopia-php/cache/issues", - "source": "https://github.com/utopia-php/cache/tree/0.10.2" + "source": "https://github.com/utopia-php/cache/tree/0.11.0" }, - "time": "2024-06-25T20:36:35+00:00" + "time": "2024-11-05T16:53:58+00:00" }, { "name": "utopia-php/cli", @@ -1724,23 +1724,23 @@ }, { "name": "utopia-php/database", - "version": "0.53.13", + "version": "0.53.15", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "a7e5de257e36e1b804d35b307865dd4036baa33e" + "reference": "2ed56d0e889f4612e54339cf55c1b751e5fe8d8f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/a7e5de257e36e1b804d35b307865dd4036baa33e", - "reference": "a7e5de257e36e1b804d35b307865dd4036baa33e", + "url": "https://api.github.com/repos/utopia-php/database/zipball/2ed56d0e889f4612e54339cf55c1b751e5fe8d8f", + "reference": "2ed56d0e889f4612e54339cf55c1b751e5fe8d8f", "shasum": "" }, "require": { "ext-mbstring": "*", "ext-pdo": "*", "php": ">=8.3", - "utopia-php/cache": "0.10.*", + "utopia-php/cache": "0.11.*", "utopia-php/framework": "0.33.*", "utopia-php/mongo": "0.3.*" }, @@ -1774,9 +1774,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.53.13" + "source": "https://github.com/utopia-php/database/tree/0.53.15" }, - "time": "2024-11-05T10:08:05+00:00" + "time": "2024-11-06T01:48:19+00:00" }, { "name": "utopia-php/domains", @@ -2495,16 +2495,16 @@ }, { "name": "utopia-php/queue", - "version": "0.7.0", + "version": "0.7.1", "source": { "type": "git", "url": "https://github.com/utopia-php/queue.git", - "reference": "917565256eb94bcab7246f7a746b1a486813761b" + "reference": "94c240d9f6383829807ce7b2d737f04b159fd3e8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/queue/zipball/917565256eb94bcab7246f7a746b1a486813761b", - "reference": "917565256eb94bcab7246f7a746b1a486813761b", + "url": "https://api.github.com/repos/utopia-php/queue/zipball/94c240d9f6383829807ce7b2d737f04b159fd3e8", + "reference": "94c240d9f6383829807ce7b2d737f04b159fd3e8", "shasum": "" }, "require": { @@ -2550,9 +2550,9 @@ ], "support": { "issues": "https://github.com/utopia-php/queue/issues", - "source": "https://github.com/utopia-php/queue/tree/0.7.0" + "source": "https://github.com/utopia-php/queue/tree/0.7.1" }, - "time": "2024-01-17T19:00:43+00:00" + "time": "2024-11-05T17:00:38+00:00" }, { "name": "utopia-php/registry", @@ -2770,22 +2770,22 @@ }, { "name": "utopia-php/vcs", - "version": "0.8.2", + "version": "0.8.3", "source": { "type": "git", "url": "https://github.com/utopia-php/vcs.git", - "reference": "eb9b7eade1a46a4f660e0d5a6304f7fa26ec9d18" + "reference": "a032ed0611a8f4467aeaa9484f73223074457337" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/vcs/zipball/eb9b7eade1a46a4f660e0d5a6304f7fa26ec9d18", - "reference": "eb9b7eade1a46a4f660e0d5a6304f7fa26ec9d18", + "url": "https://api.github.com/repos/utopia-php/vcs/zipball/a032ed0611a8f4467aeaa9484f73223074457337", + "reference": "a032ed0611a8f4467aeaa9484f73223074457337", "shasum": "" }, "require": { "adhocore/jwt": "^1.1", "php": ">=8.0", - "utopia-php/cache": "^0.10.0", + "utopia-php/cache": "^0.11.0", "utopia-php/framework": "0.*.*" }, "require-dev": { @@ -2813,9 +2813,9 @@ ], "support": { "issues": "https://github.com/utopia-php/vcs/issues", - "source": "https://github.com/utopia-php/vcs/tree/0.8.2" + "source": "https://github.com/utopia-php/vcs/tree/0.8.3" }, - "time": "2024-08-13T14:36:30+00:00" + "time": "2024-11-05T17:10:09+00:00" }, { "name": "utopia-php/websocket", @@ -4004,16 +4004,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.4.1", + "version": "5.5.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c" + "reference": "54e10d44fc1a84e2598d26f70d4f6f1f233e228a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", - "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/54e10d44fc1a84e2598d26f70d4f6f1f233e228a", + "reference": "54e10d44fc1a84e2598d26f70d4f6f1f233e228a", "shasum": "" }, "require": { @@ -4026,13 +4026,13 @@ "webmozart/assert": "^1.9.1" }, "require-dev": { - "mockery/mockery": "~1.3.5", + "mockery/mockery": "~1.3.5 || ~1.6.0", "phpstan/extension-installer": "^1.1", "phpstan/phpstan": "^1.8", "phpstan/phpstan-mockery": "^1.1", "phpstan/phpstan-webmozart-assert": "^1.2", "phpunit/phpunit": "^9.5", - "vimeo/psalm": "^5.13" + "psalm/phar": "^5.26" }, "type": "library", "extra": { @@ -4062,9 +4062,9 @@ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.4.1" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.5.0" }, - "time": "2024-05-21T05:55:05+00:00" + "time": "2024-11-04T21:26:31+00:00" }, { "name": "phpdocumentor/type-resolver", From 0f6aa3d5b1ebfef6b8f8536b5cf204f13d32853b Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 6 Nov 2024 15:45:26 +1300 Subject: [PATCH 23/28] Fix trivy scans --- .github/workflows/nightly.yml | 45 +++++++++++++++++++++++++++++++++-- .github/workflows/pr-scan.yml | 10 ++++++-- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 80d880244c..22e28f01b8 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -5,6 +5,36 @@ on: workflow_dispatch: jobs: + update-trivy-db: + runs-on: ubuntu-latest + steps: + - name: Setup oras + uses: oras-project/setup-oras@v1 + + - name: Get current date + id: date + run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT + + - name: Download and extract the vulnerability DB + run: | + mkdir -p $GITHUB_WORKSPACE/.cache/trivy/db + oras pull ghcr.io/aquasecurity/trivy-db:2 + tar -xzf db.tar.gz -C $GITHUB_WORKSPACE/.cache/trivy/db + rm db.tar.gz + + - name: Download and extract the Java DB + run: | + mkdir -p $GITHUB_WORKSPACE/.cache/trivy/java-db + oras pull ghcr.io/aquasecurity/trivy-java-db:1 + tar -xzf javadb.tar.gz -C $GITHUB_WORKSPACE/.cache/trivy/java-db + rm javadb.tar.gz + + - name: Cache DBs + uses: actions/cache/save@v4 + with: + path: ${{ github.workspace }}/.cache/trivy + key: cache-trivy-${{ steps.date.outputs.date }} + scan-image: name: Scan Docker Image runs-on: ubuntu-latest @@ -13,16 +43,22 @@ jobs: uses: actions/checkout@v4 with: submodules: recursive + - name: Build the Docker image run: docker build . -t appwrite_image:latest + - name: Run Trivy vulnerability scanner on image - uses: aquasecurity/trivy-action@0.20.0 + uses: aquasecurity/trivy-action@0.28.0 with: image-ref: 'appwrite_image:latest' format: 'sarif' output: 'trivy-image-results.sarif' ignore-unfixed: 'false' severity: 'CRITICAL,HIGH' + env: + TRIVY_SKIP_DB_UPDATE: true + TRIVY_SKIP_JAVA_DB_UPDATE: true + - name: Upload Docker Image Scan Results uses: github/codeql-action/upload-sarif@v2 with: @@ -34,13 +70,18 @@ jobs: steps: - name: Check out code uses: actions/checkout@v4 + - name: Run Trivy vulnerability scanner on filesystem - uses: aquasecurity/trivy-action@0.20.0 + uses: aquasecurity/trivy-action@0.28.0 with: scan-type: 'fs' format: 'sarif' output: 'trivy-fs-results.sarif' severity: 'CRITICAL,HIGH' + env: + TRIVY_SKIP_DB_UPDATE: true + TRIVY_SKIP_JAVA_DB_UPDATE: true + - name: Upload Code Scan Results uses: github/codeql-action/upload-sarif@v2 with: diff --git a/.github/workflows/pr-scan.yml b/.github/workflows/pr-scan.yml index eded58985d..1289efce11 100644 --- a/.github/workflows/pr-scan.yml +++ b/.github/workflows/pr-scan.yml @@ -26,21 +26,27 @@ jobs: tags: pr_image:${{ github.sha }} - name: Run Trivy vulnerability scanner on image - uses: aquasecurity/trivy-action@0.20.0 + uses: aquasecurity/trivy-action@0.28.0 with: image-ref: 'pr_image:${{ github.sha }}' format: 'json' output: 'trivy-image-results.json' severity: 'CRITICAL,HIGH' + env: + TRIVY_SKIP_DB_UPDATE: true + TRIVY_SKIP_JAVA_DB_UPDATE: true - name: Run Trivy vulnerability scanner on source code - uses: aquasecurity/trivy-action@0.20.0 + uses: aquasecurity/trivy-action@0.28.0 with: scan-type: 'fs' scan-ref: '.' format: 'json' output: 'trivy-fs-results.json' severity: 'CRITICAL,HIGH' + env: + TRIVY_SKIP_DB_UPDATE: true + TRIVY_SKIP_JAVA_DB_UPDATE: true - name: Process Trivy scan results id: process-results From 90f63a30f61097d1c33ace3eac284953209c8956 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 6 Nov 2024 16:24:55 +1300 Subject: [PATCH 24/28] Revert "Fix trivy scans" This reverts commit 0f6aa3d5b1ebfef6b8f8536b5cf204f13d32853b. --- .github/workflows/nightly.yml | 45 ++--------------------------------- .github/workflows/pr-scan.yml | 10 ++------ 2 files changed, 4 insertions(+), 51 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 22e28f01b8..80d880244c 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -5,36 +5,6 @@ on: workflow_dispatch: jobs: - update-trivy-db: - runs-on: ubuntu-latest - steps: - - name: Setup oras - uses: oras-project/setup-oras@v1 - - - name: Get current date - id: date - run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT - - - name: Download and extract the vulnerability DB - run: | - mkdir -p $GITHUB_WORKSPACE/.cache/trivy/db - oras pull ghcr.io/aquasecurity/trivy-db:2 - tar -xzf db.tar.gz -C $GITHUB_WORKSPACE/.cache/trivy/db - rm db.tar.gz - - - name: Download and extract the Java DB - run: | - mkdir -p $GITHUB_WORKSPACE/.cache/trivy/java-db - oras pull ghcr.io/aquasecurity/trivy-java-db:1 - tar -xzf javadb.tar.gz -C $GITHUB_WORKSPACE/.cache/trivy/java-db - rm javadb.tar.gz - - - name: Cache DBs - uses: actions/cache/save@v4 - with: - path: ${{ github.workspace }}/.cache/trivy - key: cache-trivy-${{ steps.date.outputs.date }} - scan-image: name: Scan Docker Image runs-on: ubuntu-latest @@ -43,22 +13,16 @@ jobs: uses: actions/checkout@v4 with: submodules: recursive - - name: Build the Docker image run: docker build . -t appwrite_image:latest - - name: Run Trivy vulnerability scanner on image - uses: aquasecurity/trivy-action@0.28.0 + uses: aquasecurity/trivy-action@0.20.0 with: image-ref: 'appwrite_image:latest' format: 'sarif' output: 'trivy-image-results.sarif' ignore-unfixed: 'false' severity: 'CRITICAL,HIGH' - env: - TRIVY_SKIP_DB_UPDATE: true - TRIVY_SKIP_JAVA_DB_UPDATE: true - - name: Upload Docker Image Scan Results uses: github/codeql-action/upload-sarif@v2 with: @@ -70,18 +34,13 @@ jobs: steps: - name: Check out code uses: actions/checkout@v4 - - name: Run Trivy vulnerability scanner on filesystem - uses: aquasecurity/trivy-action@0.28.0 + uses: aquasecurity/trivy-action@0.20.0 with: scan-type: 'fs' format: 'sarif' output: 'trivy-fs-results.sarif' severity: 'CRITICAL,HIGH' - env: - TRIVY_SKIP_DB_UPDATE: true - TRIVY_SKIP_JAVA_DB_UPDATE: true - - name: Upload Code Scan Results uses: github/codeql-action/upload-sarif@v2 with: diff --git a/.github/workflows/pr-scan.yml b/.github/workflows/pr-scan.yml index 1289efce11..eded58985d 100644 --- a/.github/workflows/pr-scan.yml +++ b/.github/workflows/pr-scan.yml @@ -26,27 +26,21 @@ jobs: tags: pr_image:${{ github.sha }} - name: Run Trivy vulnerability scanner on image - uses: aquasecurity/trivy-action@0.28.0 + uses: aquasecurity/trivy-action@0.20.0 with: image-ref: 'pr_image:${{ github.sha }}' format: 'json' output: 'trivy-image-results.json' severity: 'CRITICAL,HIGH' - env: - TRIVY_SKIP_DB_UPDATE: true - TRIVY_SKIP_JAVA_DB_UPDATE: true - name: Run Trivy vulnerability scanner on source code - uses: aquasecurity/trivy-action@0.28.0 + uses: aquasecurity/trivy-action@0.20.0 with: scan-type: 'fs' scan-ref: '.' format: 'json' output: 'trivy-fs-results.json' severity: 'CRITICAL,HIGH' - env: - TRIVY_SKIP_DB_UPDATE: true - TRIVY_SKIP_JAVA_DB_UPDATE: true - name: Process Trivy scan results id: process-results From 59674fa90891f6065f79499432916e6d3c883e79 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 6 Nov 2024 16:32:31 +1300 Subject: [PATCH 25/28] Update database --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index dbd1575919..a04ca51d43 100644 --- a/composer.json +++ b/composer.json @@ -51,7 +51,7 @@ "utopia-php/cache": "0.11.*", "utopia-php/cli": "0.15.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "0.53.15", + "utopia-php/database": "0.53.16", "utopia-php/domains": "0.5.*", "utopia-php/dsn": "0.2.1", "utopia-php/framework": "0.33.*", diff --git a/composer.lock b/composer.lock index 995afcd426..6dce436601 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "fb924a3640fb2c2e6e273718415f8205", + "content-hash": "b358198535c1867eabed7c0f99135a57", "packages": [ { "name": "adhocore/jwt", @@ -1724,16 +1724,16 @@ }, { "name": "utopia-php/database", - "version": "0.53.15", + "version": "0.53.16", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "2ed56d0e889f4612e54339cf55c1b751e5fe8d8f" + "reference": "6661edffeef05b59e16d102b989a72f7f78cf7de" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/2ed56d0e889f4612e54339cf55c1b751e5fe8d8f", - "reference": "2ed56d0e889f4612e54339cf55c1b751e5fe8d8f", + "url": "https://api.github.com/repos/utopia-php/database/zipball/6661edffeef05b59e16d102b989a72f7f78cf7de", + "reference": "6661edffeef05b59e16d102b989a72f7f78cf7de", "shasum": "" }, "require": { @@ -1774,9 +1774,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.53.15" + "source": "https://github.com/utopia-php/database/tree/0.53.16" }, - "time": "2024-11-06T01:48:19+00:00" + "time": "2024-11-06T03:07:16+00:00" }, { "name": "utopia-php/domains", From 46af6dc3f02e8a30a73e32664801ee860351f446 Mon Sep 17 00:00:00 2001 From: fogelito Date: Wed, 6 Nov 2024 09:36:27 +0200 Subject: [PATCH 26/28] Fix permissions --- app/controllers/api/databases.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 84a311e342..0114fd343c 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -816,22 +816,21 @@ App::post('/v1/databases/:databaseId/collections') $collectionId = $collectionId == 'unique()' ? ID::unique() : $collectionId; // Map aggregate permissions into the multiple permissions they represent. - $permissions = Permission::aggregate($permissions); + $permissions = Permission::aggregate($permissions) ?? []; try { - $dbForProject->createDocument('database_' . $database->getInternalId(), new Document([ + $collection = $dbForProject->createDocument('database_' . $database->getInternalId(), new Document([ '$id' => $collectionId, 'databaseInternalId' => $database->getInternalId(), 'databaseId' => $databaseId, - '$permissions' => $permissions ?? [], + '$permissions' => $permissions, 'documentSecurity' => $documentSecurity, 'enabled' => $enabled, 'name' => $name, 'search' => implode(' ', [$collectionId, $name]), ])); - $collection = $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId); - $dbForProject->createCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), permissions: $permissions ?? [], documentSecurity: $documentSecurity); + $dbForProject->createCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), permissions: $permissions, documentSecurity: $documentSecurity); } catch (DuplicateException) { throw new Exception(Exception::COLLECTION_ALREADY_EXISTS); } catch (LimitException) { From e219ee2d842317fc6200a5556307eadcb5db6fdd Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 6 Nov 2024 10:02:11 +0000 Subject: [PATCH 27/28] upgrade utopia storage --- composer.lock | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/composer.lock b/composer.lock index 6dce436601..39f5753510 100644 --- a/composer.lock +++ b/composer.lock @@ -2608,16 +2608,16 @@ }, { "name": "utopia-php/storage", - "version": "0.18.5", + "version": "0.18.6", "source": { "type": "git", "url": "https://github.com/utopia-php/storage.git", - "reference": "7d355c5e3ccc8ecebc0266f8ddd30088a43be919" + "reference": "893ccf06e183f8ece2aed8dbf14d64d6ba036071" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/storage/zipball/7d355c5e3ccc8ecebc0266f8ddd30088a43be919", - "reference": "7d355c5e3ccc8ecebc0266f8ddd30088a43be919", + "url": "https://api.github.com/repos/utopia-php/storage/zipball/893ccf06e183f8ece2aed8dbf14d64d6ba036071", + "reference": "893ccf06e183f8ece2aed8dbf14d64d6ba036071", "shasum": "" }, "require": { @@ -2657,9 +2657,9 @@ ], "support": { "issues": "https://github.com/utopia-php/storage/issues", - "source": "https://github.com/utopia-php/storage/tree/0.18.5" + "source": "https://github.com/utopia-php/storage/tree/0.18.6" }, - "time": "2024-09-04T08:57:27+00:00" + "time": "2024-11-06T09:58:50+00:00" }, { "name": "utopia-php/swoole", @@ -5875,16 +5875,16 @@ }, { "name": "symfony/console", - "version": "v7.1.6", + "version": "v7.1.7", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "bb5192af6edc797cbab5c8e8ecfea2fe5f421e57" + "reference": "3284aafcac338b6e86fd955ee4d794cbe434151a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/bb5192af6edc797cbab5c8e8ecfea2fe5f421e57", - "reference": "bb5192af6edc797cbab5c8e8ecfea2fe5f421e57", + "url": "https://api.github.com/repos/symfony/console/zipball/3284aafcac338b6e86fd955ee4d794cbe434151a", + "reference": "3284aafcac338b6e86fd955ee4d794cbe434151a", "shasum": "" }, "require": { @@ -5948,7 +5948,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.1.6" + "source": "https://github.com/symfony/console/tree/v7.1.7" }, "funding": [ { @@ -5964,7 +5964,7 @@ "type": "tidelift" } ], - "time": "2024-10-09T08:46:59+00:00" + "time": "2024-11-05T15:34:55+00:00" }, { "name": "symfony/deprecation-contracts", @@ -6546,16 +6546,16 @@ }, { "name": "symfony/process", - "version": "v7.1.6", + "version": "v7.1.7", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "6aaa189ddb4ff6b5de8fa3210f2fb42c87b4d12e" + "reference": "9b8a40b7289767aa7117e957573c2a535efe6585" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/6aaa189ddb4ff6b5de8fa3210f2fb42c87b4d12e", - "reference": "6aaa189ddb4ff6b5de8fa3210f2fb42c87b4d12e", + "url": "https://api.github.com/repos/symfony/process/zipball/9b8a40b7289767aa7117e957573c2a535efe6585", + "reference": "9b8a40b7289767aa7117e957573c2a535efe6585", "shasum": "" }, "require": { @@ -6587,7 +6587,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.1.6" + "source": "https://github.com/symfony/process/tree/v7.1.7" }, "funding": [ { @@ -6603,7 +6603,7 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-11-06T09:25:12+00:00" }, { "name": "symfony/service-contracts", From b410b17ba61e451c0430c7fe5b3f28bec4236c95 Mon Sep 17 00:00:00 2001 From: Fabian Gruber Date: Thu, 31 Oct 2024 13:12:03 +0100 Subject: [PATCH 28/28] feat: redis-cluster support --- app/controllers/api/health.php | 6 ++-- app/init.php | 6 +++- app/realtime.php | 9 +++--- docker-compose.yml | 2 +- src/Appwrite/Messaging/Adapter/Realtime.php | 35 ++++++++++++--------- src/Appwrite/Platform/Tasks/Doctor.php | 2 ++ src/Appwrite/PubSub/Adapter.php | 13 ++++++++ src/Appwrite/PubSub/Adapter/Redis.php | 31 ++++++++++++++++++ tests/unit/Event/EventTest.php | 19 ++--------- tests/unit/Usage/StatsTest.php | 19 ++--------- 10 files changed, 85 insertions(+), 57 deletions(-) create mode 100644 src/Appwrite/PubSub/Adapter.php create mode 100644 src/Appwrite/PubSub/Adapter/Redis.php diff --git a/app/controllers/api/health.php b/app/controllers/api/health.php index f4581df8e4..60a8c0ca97 100644 --- a/app/controllers/api/health.php +++ b/app/controllers/api/health.php @@ -135,6 +135,7 @@ App::get('/v1/health/cache') foreach ($configs as $key => $config) { foreach ($config as $database) { try { + /** @var \Utopia\Cache\Adapter $adapter */ $adapter = $pools->get($database)->pop()->getResource(); $checkStart = \microtime(true); @@ -191,11 +192,11 @@ App::get('/v1/health/queue') foreach ($configs as $key => $config) { foreach ($config as $database) { + $checkStart = \microtime(true); try { + /** @var Connection $adapter */ $adapter = $pools->get($database)->pop()->getResource(); - $checkStart = \microtime(true); - if ($adapter->ping()) { $output[] = new Document([ 'name' => $key . " ($database)", @@ -249,6 +250,7 @@ App::get('/v1/health/pubsub') foreach ($configs as $key => $config) { foreach ($config as $database) { try { + /** @var \Appwrite\PubSub\Adapter $adapter */ $adapter = $pools->get($database)->pop()->getResource(); $checkStart = \microtime(true); diff --git a/app/init.php b/app/init.php index d062e218e9..c9ec2e0061 100644 --- a/app/init.php +++ b/app/init.php @@ -42,6 +42,7 @@ use Appwrite\Hooks\Hooks; use Appwrite\Network\Validator\Email; use Appwrite\Network\Validator\Origin; use Appwrite\OpenSSL\OpenSSL; +use Appwrite\PubSub\Adapter\Redis as PubSub; use Appwrite\URL\URL as AppwriteURL; use Appwrite\Utopia\Request; use MaxMind\Db\Reader; @@ -973,7 +974,10 @@ $register->set('pools', function () { $adapter->setDatabase($dsn->getPath()); break; case 'pubsub': - $adapter = $resource(); + $adapter = match ($dsn->getScheme()) { + 'redis' => new PubSub($resource()), + default => null + }; break; case 'queue': $adapter = match ($dsn->getScheme()) { diff --git a/app/realtime.php b/app/realtime.php index 1b59eb3bc7..d38192b83c 100644 --- a/app/realtime.php +++ b/app/realtime.php @@ -365,17 +365,16 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, } $start = time(); - $redis = $register->get('pools')->get('pubsub')->pop()->getResource(); /** @var Redis $redis */ - $redis->setOption(Redis::OPT_READ_TIMEOUT, -1); - - if ($redis->ping(true)) { + /** @var \Appwrite\PubSub\Adapter $pubsub */ + $pubsub = $register->get('pools')->get('pubsub')->pop()->getResource(); + if ($pubsub->ping(true)) { $attempts = 0; Console::success('Pub/sub connection established (worker: ' . $workerId . ')'); } else { Console::error('Pub/sub failed (worker: ' . $workerId . ')'); } - $redis->subscribe(['realtime'], function (Redis $redis, string $channel, string $payload) use ($server, $workerId, $stats, $register, $realtime) { + $pubsub->subscribe(['realtime'], function (mixed $redis, string $channel, string $payload) use ($server, $workerId, $stats, $register, $realtime) { $event = json_decode($payload, true); if ($event['permissionsChanged'] && isset($event['userId'])) { diff --git a/docker-compose.yml b/docker-compose.yml index 479ca38b8f..f2845dc137 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1053,4 +1053,4 @@ volumes: appwrite-certificates: appwrite-functions: appwrite-builds: - appwrite-config: + appwrite-config: \ No newline at end of file diff --git a/src/Appwrite/Messaging/Adapter/Realtime.php b/src/Appwrite/Messaging/Adapter/Realtime.php index c437d4d487..dceafacf6e 100644 --- a/src/Appwrite/Messaging/Adapter/Realtime.php +++ b/src/Appwrite/Messaging/Adapter/Realtime.php @@ -7,7 +7,6 @@ use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Role; -use Utopia\System\System; class Realtime extends Adapter { @@ -139,20 +138,26 @@ class Realtime extends Adapter $permissionsChanged = array_key_exists('permissionsChanged', $options) && $options['permissionsChanged']; $userId = array_key_exists('userId', $options) ? $options['userId'] : null; - $redis = new \Redis(); //TODO: make this part of the constructor - $redis->connect(System::getEnv('_APP_REDIS_HOST', ''), System::getEnv('_APP_REDIS_PORT', '')); - $redis->publish('realtime', json_encode([ - 'project' => $projectId, - 'roles' => $roles, - 'permissionsChanged' => $permissionsChanged, - 'userId' => $userId, - 'data' => [ - 'events' => $events, - 'channels' => $channels, - 'timestamp' => DateTime::formatTz(DateTime::now()), - 'payload' => $payload - ] - ])); + global $register; + $pubsub = $register->get('pools')->get('pubsub')->pop(); + try { + /** @var \Appwrite\PubSub\Adapter $redis */ + $redis = $pubsub->getResource(); + $redis->publish('realtime', json_encode([ + 'project' => $projectId, + 'roles' => $roles, + 'permissionsChanged' => $permissionsChanged, + 'userId' => $userId, + 'data' => [ + 'events' => $events, + 'channels' => $channels, + 'timestamp' => DateTime::formatTz(DateTime::now()), + 'payload' => $payload + ] + ])); + } finally { + $pubsub->reclaim(); + } } /** diff --git a/src/Appwrite/Platform/Tasks/Doctor.php b/src/Appwrite/Platform/Tasks/Doctor.php index 82d1ca2d59..c43afea527 100644 --- a/src/Appwrite/Platform/Tasks/Doctor.php +++ b/src/Appwrite/Platform/Tasks/Doctor.php @@ -3,6 +3,7 @@ namespace Appwrite\Platform\Tasks; use Appwrite\ClamAV\Network; +use Appwrite\PubSub\Adapter; use Utopia\App; use Utopia\CLI\Console; use Utopia\Config\Config; @@ -158,6 +159,7 @@ class Doctor extends Action foreach ($configs as $key => $config) { foreach ($config as $pool) { try { + /** @var Adapter $adapter */ $adapter = $pools->get($pool)->pop()->getResource(); if ($adapter->ping()) { diff --git a/src/Appwrite/PubSub/Adapter.php b/src/Appwrite/PubSub/Adapter.php new file mode 100644 index 0000000000..e5ddbe5e62 --- /dev/null +++ b/src/Appwrite/PubSub/Adapter.php @@ -0,0 +1,13 @@ +client = $client; + + } + + public function ping($message = null): bool + { + return $this->client->ping($message); + } + + public function subscribe($channels, $callback) + { + return $this->client->subscribe($channels, $callback); + } + + public function publish($channel, $message) + { + return $this->client->publish($channel, $message); + } +} diff --git a/tests/unit/Event/EventTest.php b/tests/unit/Event/EventTest.php index dd9833378f..079bb47b65 100644 --- a/tests/unit/Event/EventTest.php +++ b/tests/unit/Event/EventTest.php @@ -3,13 +3,9 @@ namespace Tests\Unit\Event; use Appwrite\Event\Event; -use Appwrite\URL\URL; use InvalidArgumentException; use PHPUnit\Framework\TestCase; -use Utopia\DSN\DSN; -use Utopia\Queue; use Utopia\Queue\Client; -use Utopia\System\System; require_once __DIR__ . '/../../../app/init.php'; @@ -20,19 +16,8 @@ class EventTest extends TestCase public function setUp(): void { - $fallbackForRedis = 'redis_main=' . URL::unparse([ - 'scheme' => 'redis', - 'host' => System::getEnv('_APP_REDIS_HOST', 'redis'), - 'port' => System::getEnv('_APP_REDIS_PORT', '6379'), - 'user' => System::getEnv('_APP_REDIS_USER', ''), - 'pass' => System::getEnv('_APP_REDIS_PASS', ''), - ]); - - $dsn = System::getEnv('_APP_CONNECTIONS_QUEUE', $fallbackForRedis); - $dsn = explode('=', $dsn); - $dsn = $dsn[1] ?? ''; - $dsn = new DSN($dsn); - $connection = new Queue\Connection\Redis($dsn->getHost(), $dsn->getPort()); + global $register; + $connection = $register->get('pools')->get('queue')->pop()->getResource(); $this->queue = 'v1-tests' . uniqid(); $this->object = new Event($connection); $this->object->setClass('TestsV1'); diff --git a/tests/unit/Usage/StatsTest.php b/tests/unit/Usage/StatsTest.php index 67e39d8974..79fa1f58ec 100644 --- a/tests/unit/Usage/StatsTest.php +++ b/tests/unit/Usage/StatsTest.php @@ -2,13 +2,9 @@ namespace Tests\Unit\Usage; -use Appwrite\URL\URL as AppwriteURL; use PHPUnit\Framework\TestCase; -use Utopia\DSN\DSN; -use Utopia\Queue; use Utopia\Queue\Client; use Utopia\Queue\Connection; -use Utopia\System\System; class StatsTest extends TestCase { @@ -19,18 +15,9 @@ class StatsTest extends TestCase public function setUp(): void { - $env = System::getEnv('_APP_CONNECTIONS_QUEUE', 'redis_main=' . AppwriteURL::unparse([ - 'scheme' => 'redis', - 'host' => System::getEnv('_APP_REDIS_HOST', 'redis'), - 'port' => System::getEnv('_APP_REDIS_PORT', '6379'), - 'user' => System::getEnv('_APP_REDIS_USER', ''), - 'pass' => System::getEnv('_APP_REDIS_PASS', ''), - ])); - - $dsn = explode('=', $env); - $dsn = count($dsn) > 1 ? $dsn[1] : $dsn[0]; - $dsn = new DSN($dsn); - $this->connection = new Queue\Connection\Redis($dsn->getHost(), $dsn->getPort()); + global $register; + $connection = $register->get('pools')->get('queue')->pop()->getResource(); + $this->connection = $connection; $this->client = new Client(self::QUEUE_NAME, $this->connection); }