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/33] 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/33] 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 eacb55cdcc4f07229242237cd6723a1897185638 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 29 Oct 2024 19:04:37 +1300 Subject: [PATCH 03/33] Fix validator usage for updating string size --- app/controllers/api/databases.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 942f886417..a5083e3dfe 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -1951,7 +1951,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/strin ->param('key', '', new Key(), 'Attribute Key.') ->param('required', null, new Boolean(), 'Is attribute required?') ->param('default', null, new Nullable(new Text(0, 0)), 'Default value for attribute when not provided. Cannot be set when attribute is required.') - ->param('size', null, new Integer(), 'Maximum size of the string attribute.', true) + ->param('size', null, new Range(1, APP_DATABASE_ATTRIBUTE_STRING_MAX_LENGTH, Range::TYPE_INTEGER), 'Maximum size of the string attribute.', true) ->param('newKey', null, new Key(), 'New attribute key.', true) ->inject('response') ->inject('dbForProject') 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 04/33] 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 05/33] 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 06/33] 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 07/33] 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 08/33] 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 09/33] 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 10/33] 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 11/33] 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 12/33] 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 13/33] 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 ca5d9ce96a5b4906b2ec583af7c2b8ffb8bab533 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 4 Nov 2024 15:09:16 +1300 Subject: [PATCH 14/33] Update database --- composer.json | 2 +- composer.lock | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/composer.json b/composer.json index 04a6af2415..2fc43f7cd0 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.9", + "utopia-php/database": "0.53.10", "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 314ec397f5..aab67e93eb 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": "1529d4abfe0432b2d9c838705220ed4a", + "content-hash": "f77f899f6250f080406151dc4cb29f92", "packages": [ { "name": "adhocore/jwt", @@ -1724,16 +1724,16 @@ }, { "name": "utopia-php/database", - "version": "0.53.9", + "version": "0.53.10", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "19969d2c6d29b5d1cbf4cb1a33e18017a54f30e3" + "reference": "39550cb0e32cac45bf5ea1deb96569b837c028b1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/19969d2c6d29b5d1cbf4cb1a33e18017a54f30e3", - "reference": "19969d2c6d29b5d1cbf4cb1a33e18017a54f30e3", + "url": "https://api.github.com/repos/utopia-php/database/zipball/39550cb0e32cac45bf5ea1deb96569b837c028b1", + "reference": "39550cb0e32cac45bf5ea1deb96569b837c028b1", "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.9" + "source": "https://github.com/utopia-php/database/tree/0.53.10" }, - "time": "2024-10-31T08:18:52+00:00" + "time": "2024-11-04T02:02:23+00:00" }, { "name": "utopia-php/domains", @@ -4068,23 +4068,23 @@ }, { "name": "phpdocumentor/type-resolver", - "version": "1.8.2", + "version": "1.9.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "153ae662783729388a584b4361f2545e4d841e3c" + "reference": "1fb5ba8d045f5dd984ebded5b1cc66f29459422d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/153ae662783729388a584b4361f2545e4d841e3c", - "reference": "153ae662783729388a584b4361f2545e4d841e3c", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/1fb5ba8d045f5dd984ebded5b1cc66f29459422d", + "reference": "1fb5ba8d045f5dd984ebded5b1cc66f29459422d", "shasum": "" }, "require": { "doctrine/deprecations": "^1.0", "php": "^7.3 || ^8.0", "phpdocumentor/reflection-common": "^2.0", - "phpstan/phpdoc-parser": "^1.13" + "phpstan/phpdoc-parser": "^1.18" }, "require-dev": { "ext-tokenizer": "*", @@ -4120,9 +4120,9 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.2" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.9.0" }, - "time": "2024-02-23T11:10:43+00:00" + "time": "2024-11-03T20:11:34+00:00" }, { "name": "phpspec/prophecy", @@ -7005,7 +7005,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { From c59cd97eb86617857938790410d933aeb9ee8f31 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 4 Nov 2024 20:57:08 +1300 Subject: [PATCH 15/33] Not found exception --- app/controllers/api/databases.php | 163 ++++++++++++++---------------- app/controllers/general.php | 2 + composer.json | 2 +- composer.lock | 24 ++--- 4 files changed, 89 insertions(+), 102 deletions(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 5b2c2080cb..13cc4e5ed7 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -3,7 +3,6 @@ use Appwrite\Auth\Auth; use Appwrite\Detector\Detector; use Appwrite\Event\Database as EventDatabase; -use Appwrite\Event\Delete; use Appwrite\Event\Event; use Appwrite\Event\Usage; use Appwrite\Extend\Exception; @@ -26,6 +25,7 @@ use Utopia\Database\Exception\Authorization as AuthorizationException; use Utopia\Database\Exception\Conflict as ConflictException; use Utopia\Database\Exception\Duplicate as DuplicateException; use Utopia\Database\Exception\Limit as LimitException; +use Utopia\Database\Exception\NotFound as NotFoundException; use Utopia\Database\Exception\Query as QueryException; use Utopia\Database\Exception\Restricted as RestrictedException; use Utopia\Database\Exception\Structure as StructureException; @@ -352,13 +352,16 @@ function updateAttribute( if ($type === Database::VAR_RELATIONSHIP) { $primaryDocumentOptions = \array_merge($attribute->getAttribute('options', []), $options); $attribute->setAttribute('options', $primaryDocumentOptions); - - $dbForProject->updateRelationship( - collection: $collectionId, - id: $key, - newKey: $newKey, - onDelete: $primaryDocumentOptions['onDelete'], - ); + try { + $dbForProject->updateRelationship( + collection: $collectionId, + id: $key, + newKey: $newKey, + onDelete: $primaryDocumentOptions['onDelete'], + ); + } catch (NotFoundException) { + throw new Exception(Exception::ATTRIBUTE_NOT_FOUND); + } if ($primaryDocumentOptions['twoWay']) { $relatedCollection = $dbForProject->getDocument('database_' . $db->getInternalId(), $primaryDocumentOptions['relatedCollection']); @@ -388,6 +391,8 @@ function updateAttribute( ); } catch (TruncateException) { throw new Exception(Exception::ATTRIBUTE_INVALID_RESIZE); + } catch (NotFoundException) { + throw new Exception(Exception::ATTRIBUTE_NOT_FOUND); } } @@ -530,12 +535,7 @@ App::get('/v1/databases') ->inject('response') ->inject('dbForProject') ->action(function (array $queries, string $search, Response $response, Database $dbForProject) { - - try { - $queries = Query::parseQueries($queries); - } catch (QueryException $e) { - throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage()); - } + $queries = Query::parseQueries($queries); if (!empty($search)) { $queries[] = Query::search('search', $search); @@ -849,7 +849,7 @@ App::post('/v1/databases/:databaseId/collections') }); App::get('/v1/databases/:databaseId/collections') - ->alias('/v1/database/collections', ['databaseId' => 'default']) + ->alias('/v1/database/collections') ->desc('List collections') ->groups(['api', 'database']) ->label('scope', 'collections.read') @@ -875,11 +875,7 @@ App::get('/v1/databases/:databaseId/collections') throw new Exception(Exception::DATABASE_NOT_FOUND); } - try { - $queries = Query::parseQueries($queries); - } catch (QueryException $e) { - throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage()); - } + $queries = Query::parseQueries($queries); if (!empty($search)) { $queries[] = Query::search('search', $search); @@ -919,7 +915,7 @@ App::get('/v1/databases/:databaseId/collections') }); App::get('/v1/databases/:databaseId/collections/:collectionId') - ->alias('/v1/database/collections/:collectionId', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId') ->desc('Get collection') ->groups(['api', 'database']) ->label('scope', 'collections.read') @@ -954,7 +950,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId') }); App::get('/v1/databases/:databaseId/collections/:collectionId/logs') - ->alias('/v1/database/collections/:collectionId/logs', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId/logs') ->desc('List collection logs') ->groups(['api', 'database']) ->label('scope', 'collections.read') @@ -988,12 +984,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/logs') throw new Exception(Exception::COLLECTION_NOT_FOUND); } - try { - $queries = Query::parseQueries($queries); - } catch (QueryException $e) { - throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage()); - } - + $queries = Query::parseQueries($queries); $grouped = Query::groupByType($queries); $limit = $grouped['limit'] ?? APP_LIMIT_COUNT; $offset = $grouped['offset'] ?? 0; @@ -1055,7 +1046,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/logs') App::put('/v1/databases/:databaseId/collections/:collectionId') - ->alias('/v1/database/collections/:collectionId', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId') ->desc('Update collection') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') @@ -1106,7 +1097,8 @@ App::put('/v1/databases/:databaseId/collections/:collectionId') ->setAttribute('$permissions', $permissions) ->setAttribute('documentSecurity', $documentSecurity) ->setAttribute('enabled', $enabled) - ->setAttribute('search', implode(' ', [$collectionId, $name]))); + ->setAttribute('search', \implode(' ', [$collectionId, $name])) + ); $dbForProject->updateCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $permissions, $documentSecurity); @@ -1119,7 +1111,7 @@ App::put('/v1/databases/:databaseId/collections/:collectionId') }); App::delete('/v1/databases/:databaseId/collections/:collectionId') - ->alias('/v1/database/collections/:collectionId', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId') ->desc('Delete collection') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') @@ -1175,7 +1167,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId') }); App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string') - ->alias('/v1/database/collections/:collectionId/attributes/string', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId/attributes/string') ->desc('Create string attribute') ->groups(['api', 'database', 'schema']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') @@ -1226,14 +1218,13 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string 'filters' => $filters, ]), $response, $dbForProject, $queueForDatabase, $queueForEvents); - $response ->setStatusCode(Response::STATUS_CODE_ACCEPTED) ->dynamic($attribute, Response::MODEL_ATTRIBUTE_STRING); }); App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email') - ->alias('/v1/database/collections/:collectionId/attributes/email', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId/attributes/email') ->desc('Create email attribute') ->groups(['api', 'database', 'schema']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') @@ -1276,7 +1267,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email' }); App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum') - ->alias('/v1/database/collections/:collectionId/attributes/enum', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId/attributes/enum') ->desc('Create enum attribute') ->groups(['api', 'database', 'schema']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') @@ -1324,7 +1315,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum') }); App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip') - ->alias('/v1/database/collections/:collectionId/attributes/ip', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId/attributes/ip') ->desc('Create IP address attribute') ->groups(['api', 'database', 'schema']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') @@ -1367,7 +1358,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip') }); App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url') - ->alias('/v1/database/collections/:collectionId/attributes/url', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId/attributes/url') ->desc('Create URL attribute') ->groups(['api', 'database', 'schema']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') @@ -1410,7 +1401,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url') }); App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/integer') - ->alias('/v1/database/collections/:collectionId/attributes/integer', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId/attributes/integer') ->desc('Create integer attribute') ->groups(['api', 'database', 'schema']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') @@ -1440,8 +1431,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/intege ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?int $min, ?int $max, ?int $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) { // Ensure attribute default is within range - $min = (is_null($min)) ? PHP_INT_MIN : \intval($min); - $max = (is_null($max)) ? PHP_INT_MAX : \intval($max); + $min = \is_null($min) ? PHP_INT_MIN : $min; + $max = \is_null($max) ? PHP_INT_MAX : $max; if ($min > $max) { throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, 'Minimum value must be lesser than maximum value'); @@ -1482,7 +1473,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/intege }); App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float') - ->alias('/v1/database/collections/:collectionId/attributes/float', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId/attributes/float') ->desc('Create float attribute') ->groups(['api', 'database', 'schema']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') @@ -1512,21 +1503,16 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float' ->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?float $min, ?float $max, ?float $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) { // Ensure attribute default is within range - $min = (is_null($min)) ? -PHP_FLOAT_MAX : \floatval($min); - $max = (is_null($max)) ? PHP_FLOAT_MAX : \floatval($max); + $min = \is_null($min) ? -PHP_FLOAT_MAX : $min; + $max = \is_null($max) ? PHP_FLOAT_MAX : $max; if ($min > $max) { throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, 'Minimum value must be lesser than maximum value'); } - // Ensure default value is a float - if (!is_null($default)) { - $default = \floatval($default); - } - $validator = new Range($min, $max, Database::VAR_FLOAT); - if (!is_null($default) && !$validator->isValid($default)) { + if (!\is_null($default) && !$validator->isValid($default)) { throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, $validator->getDescription()); } @@ -1557,7 +1543,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float' }); App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/boolean') - ->alias('/v1/database/collections/:collectionId/attributes/boolean', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId/attributes/boolean') ->desc('Create boolean attribute') ->groups(['api', 'database', 'schema']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') @@ -1599,7 +1585,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/boolea }); App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/datetime') - ->alias('/v1/database/collections/:collectionId/attributes/datetime', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId/attributes/datetime') ->desc('Create datetime attribute') ->groups(['api', 'database']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') @@ -1644,7 +1630,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/dateti }); App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relationship') - ->alias('/v1/database/collections/:collectionId/attributes/relationship', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId/attributes/relationship') ->desc('Create relationship attribute') ->groups(['api', 'database']) ->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create') @@ -1773,7 +1759,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relati }); App::get('/v1/databases/:databaseId/collections/:collectionId/attributes') - ->alias('/v1/database/collections/:collectionId/attributes', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId/attributes') ->desc('List attributes') ->groups(['api', 'database']) ->label('scope', 'collections.read') @@ -1804,16 +1790,12 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes') throw new Exception(Exception::COLLECTION_NOT_FOUND); } - try { - $queries = Query::parseQueries($queries); - } catch (QueryException $e) { - throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage()); - } + $queries = Query::parseQueries($queries); \array_push( $queries, + Query::equal('databaseInternalId', [$database->getInternalId()]), Query::equal('collectionInternalId', [$collection->getInternalId()]), - Query::equal('databaseInternalId', [$database->getInternalId()]) ); /** @@ -1822,6 +1804,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes') $cursor = \array_filter($queries, function ($query) { return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]); }); + $cursor = \reset($cursor); if ($cursor) { @@ -1832,8 +1815,8 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes') $attributeId = $cursor->getValue(); $cursorDocument = Authorization::skip(fn () => $dbForProject->find('attributes', [ - Query::equal('collectionInternalId', [$collection->getInternalId()]), Query::equal('databaseInternalId', [$database->getInternalId()]), + Query::equal('collectionInternalId', [$collection->getInternalId()]), Query::equal('key', [$attributeId]), Query::limit(1), ])); @@ -1857,7 +1840,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes') }); App::get('/v1/databases/:databaseId/collections/:collectionId/attributes/:key') - ->alias('/v1/database/collections/:collectionId/attributes/:key', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId/attributes/:key') ->desc('Get attribute') ->groups(['api', 'database']) ->label('scope', 'collections.read') @@ -2390,7 +2373,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/:key/ }); App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key') - ->alias('/v1/database/collections/:collectionId/attributes/:key', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId/attributes/:key') ->desc('Delete attribute') ->groups(['api', 'database', 'schema']) ->label('scope', 'collections.write') @@ -2504,7 +2487,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key }); App::post('/v1/databases/:databaseId/collections/:collectionId/indexes') - ->alias('/v1/database/collections/:collectionId/indexes', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId/indexes') ->desc('Create index') ->groups(['api', 'database']) ->label('event', 'databases.[databaseId].collections.[collectionId].indexes.[indexId].create') @@ -2675,7 +2658,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes') }); App::get('/v1/databases/:databaseId/collections/:collectionId/indexes') - ->alias('/v1/database/collections/:collectionId/indexes', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId/indexes') ->desc('List indexes') ->groups(['api', 'database']) ->label('scope', 'collections.read') @@ -2706,13 +2689,13 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes') throw new Exception(Exception::COLLECTION_NOT_FOUND); } - try { - $queries = Query::parseQueries($queries); - } catch (QueryException $e) { - throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage()); - } + $queries = Query::parseQueries($queries); - \array_push($queries, Query::equal('collectionId', [$collectionId]), Query::equal('databaseId', [$databaseId])); + \array_push( + $queries, + Query::equal('databaseId', [$databaseId]), + Query::equal('collectionId', [$collectionId]), + ); /** * Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries @@ -3042,10 +3025,12 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents') try { $document = $dbForProject->createDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $document); - } catch (StructureException $exception) { - throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $exception->getMessage()); - } catch (DuplicateException $exception) { + } catch (StructureException $e) { + throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage()); + } catch (DuplicateException $e) { throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS); + } catch (NotFoundException $e) { + throw new Exception(Exception::COLLECTION_NOT_FOUND); } // Add $collectionId and $databaseId for all documents @@ -3176,14 +3161,8 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') $cursor->setValue($cursorDocument); } - try { - $documents = $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries); - $total = $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries, APP_LIMIT_COUNT); - } catch (AuthorizationException) { - throw new Exception(Exception::USER_UNAUTHORIZED); - } catch (QueryException $e) { - throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage()); - } + $documents = $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries); + $total = $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries, APP_LIMIT_COUNT); // Add $collectionId and $databaseId for all documents $processDocument = (function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database): bool { @@ -3647,8 +3626,10 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum throw new Exception(Exception::USER_UNAUTHORIZED); } catch (DuplicateException) { throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS); - } catch (StructureException $exception) { - throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $exception->getMessage()); + } catch (StructureException $e) { + throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage()); + } catch (NotFoundException $e) { + throw new Exception(Exception::COLLECTION_NOT_FOUND); } // Add $collectionId and $databaseId for all documents @@ -3757,12 +3738,16 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu throw new Exception(Exception::DOCUMENT_NOT_FOUND); } - $dbForProject->withRequestTimestamp($requestTimestamp, function () use ($dbForProject, $database, $collection, $documentId) { - $dbForProject->deleteDocument( - 'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), - $documentId - ); - }); + try { + $dbForProject->withRequestTimestamp($requestTimestamp, function () use ($dbForProject, $database, $collection, $documentId) { + $dbForProject->deleteDocument( + 'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), + $documentId + ); + }); + } catch (NotFoundException $e) { + throw new Exception(Exception::COLLECTION_NOT_FOUND); + } // Add $collectionId and $databaseId for all documents $processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database) { diff --git a/app/controllers/general.php b/app/controllers/general.php index 7763566159..b08ecb3d12 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -775,6 +775,8 @@ App::error() case 'Utopia\Database\Exception\Relationship': $error = new AppwriteException(AppwriteException::RELATIONSHIP_VALUE_INVALID, $error->getMessage(), previous: $error); break; + case 'Utopia\Database\Exception\NotFound': + $error = new AppwriteException(AppwriteException::COLLECTION_NOT_FOUND, $error->getMessage(), previous: $error); } $code = $error->getCode(); diff --git a/composer.json b/composer.json index 2fc43f7cd0..f122149ec2 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.10", + "utopia-php/database": "0.53.12", "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 aab67e93eb..0650710611 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": "f77f899f6250f080406151dc4cb29f92", + "content-hash": "102bab0efa76adc92312b9c5a7f488e3", "packages": [ { "name": "adhocore/jwt", @@ -1724,32 +1724,32 @@ }, { "name": "utopia-php/database", - "version": "0.53.10", + "version": "0.53.12", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "39550cb0e32cac45bf5ea1deb96569b837c028b1" + "reference": "4068d0c5c503ec4562f8c0aa9cfd095889418b83" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/39550cb0e32cac45bf5ea1deb96569b837c028b1", - "reference": "39550cb0e32cac45bf5ea1deb96569b837c028b1", + "url": "https://api.github.com/repos/utopia-php/database/zipball/4068d0c5c503ec4562f8c0aa9cfd095889418b83", + "reference": "4068d0c5c503ec4562f8c0aa9cfd095889418b83", "shasum": "" }, "require": { "ext-mbstring": "*", "ext-pdo": "*", - "php": ">=8.0", + "php": ">=8.3", "utopia-php/cache": "0.10.*", "utopia-php/framework": "0.33.*", "utopia-php/mongo": "0.3.*" }, "require-dev": { "fakerphp/faker": "1.23.*", - "laravel/pint": "1.17.*", - "pcov/clobber": "2.0.*", - "phpstan/phpstan": "1.11.*", - "phpunit/phpunit": "9.6.*", + "laravel/pint": "1.*", + "pcov/clobber": "2.*", + "phpstan/phpstan": "1.*", + "phpunit/phpunit": "9.*", "rregeer/phpunit-coverage-check": "0.3.*", "swoole/ide-helper": "5.1.3", "utopia-php/cli": "0.14.*" @@ -1774,9 +1774,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.53.10" + "source": "https://github.com/utopia-php/database/tree/0.53.12" }, - "time": "2024-11-04T02:02:23+00:00" + "time": "2024-11-04T07:51:44+00:00" }, { "name": "utopia-php/domains", From b6287e11a35db76bc1da59ef25c48c969f873375 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 4 Nov 2024 21:40:02 +1300 Subject: [PATCH 16/33] Add storage catches --- app/controllers/api/databases.php | 5 +- app/controllers/api/storage.php | 89 ++++++++++++++++++------------- 2 files changed, 55 insertions(+), 39 deletions(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 13cc4e5ed7..7c10662946 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -1092,7 +1092,10 @@ App::put('/v1/databases/:databaseId/collections/:collectionId') $enabled ??= $collection->getAttribute('enabled', true); - $collection = $dbForProject->updateDocument('database_' . $database->getInternalId(), $collectionId, $collection + $collection = $dbForProject->updateDocument( + 'database_' . $database->getInternalId(), + $collectionId, + $collection ->setAttribute('name', $name) ->setAttribute('$permissions', $permissions) ->setAttribute('documentSecurity', $documentSecurity) diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 23dd21c173..6b8248f101 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -16,7 +16,8 @@ use Utopia\App; use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\Document; -use Utopia\Database\Exception\Duplicate; +use Utopia\Database\Exception\Duplicate as DuplicateException; +use Utopia\Database\Exception\NotFound as NotFoundException; use Utopia\Database\Exception\Query as QueryException; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; @@ -131,7 +132,7 @@ App::post('/v1/storage/buckets') $bucket = $dbForProject->getDocument('buckets', $bucketId); $dbForProject->createCollection('bucket_' . $bucket->getInternalId(), $attributes, $indexes, permissions: $permissions ?? [], documentSecurity: $fileSecurity); - } catch (Duplicate) { + } catch (DuplicateException) { throw new Exception(Exception::STORAGE_BUCKET_ALREADY_EXISTS); } @@ -273,10 +274,6 @@ App::put('/v1/storage/buckets/:bucketId') $encryption ??= $bucket->getAttribute('encryption', true); $antivirus ??= $bucket->getAttribute('antivirus', true); - /** - * Map aggregate permissions into the multiple permissions they represent, - * accounting for the resource type given that some types not allowed specific permissions. - */ // Map aggregate permissions into the multiple permissions they represent. $permissions = Permission::aggregate($permissions); @@ -290,11 +287,11 @@ App::put('/v1/storage/buckets/:bucketId') ->setAttribute('encryption', $encryption) ->setAttribute('compression', $compression) ->setAttribute('antivirus', $antivirus)); + $dbForProject->updateCollection('bucket_' . $bucket->getInternalId(), $permissions, $fileSecurity); $queueForEvents - ->setParam('bucketId', $bucket->getId()) - ; + ->setParam('bucketId', $bucket->getId()); $response->dynamic($bucket, Response::MODEL_BUCKET); }); @@ -342,7 +339,7 @@ App::delete('/v1/storage/buckets/:bucketId') }); App::post('/v1/storage/buckets/:bucketId/files') - ->alias('/v1/storage/files', ['bucketId' => 'default']) + ->alias('/v1/storage/files') ->desc('Create file') ->groups(['api', 'storage']) ->label('scope', 'files.write') @@ -670,7 +667,11 @@ App::post('/v1/storage/buckets/:bucketId/files') 'metadata' => $metadata, ]); - $file = $dbForProject->createDocument('bucket_' . $bucket->getInternalId(), $doc); + try { + $file = $dbForProject->createDocument('bucket_' . $bucket->getInternalId(), $doc); + } catch (NotFoundException) { + throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); + } } else { $file = $file ->setAttribute('chunksUploaded', $chunksUploaded) @@ -686,15 +687,19 @@ App::post('/v1/storage/buckets/:bucketId/files') if (!$validator->isValid($bucket->getCreate())) { throw new Exception(Exception::USER_UNAUTHORIZED); } - $file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file)); + + try { + $file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file)); + } catch (NotFoundException) { + throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); + } } } $queueForEvents ->setParam('bucketId', $bucket->getId()) ->setParam('fileId', $file->getId()) - ->setContext('bucket', $bucket) - ; + ->setContext('bucket', $bucket); $metadata = null; // was causing leaks as it was passed by reference @@ -704,7 +709,7 @@ App::post('/v1/storage/buckets/:bucketId/files') }); App::get('/v1/storage/buckets/:bucketId/files') - ->alias('/v1/storage/files', ['bucketId' => 'default']) + ->alias('/v1/storage/files') ->desc('List files') ->groups(['api', 'storage']) ->label('scope', 'files.read') @@ -739,11 +744,7 @@ App::get('/v1/storage/buckets/:bucketId/files') throw new Exception(Exception::USER_UNAUTHORIZED); } - try { - $queries = Query::parseQueries($queries); - } catch (QueryException $e) { - throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage()); - } + $queries = Query::parseQueries($queries); if (!empty($search)) { $queries[] = Query::search('search', $search); @@ -781,12 +782,16 @@ App::get('/v1/storage/buckets/:bucketId/files') $filterQueries = Query::groupByType($queries)['filters']; - if ($fileSecurity && !$valid) { - $files = $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries); - $total = $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT); - } else { - $files = Authorization::skip(fn () => $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries)); - $total = Authorization::skip(fn () => $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT)); + try { + if ($fileSecurity && !$valid) { + $files = $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries); + $total = $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT); + } else { + $files = Authorization::skip(fn() => $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries)); + $total = Authorization::skip(fn() => $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT)); + } + } catch (NotFoundException) { + throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } $response->dynamic(new Document([ @@ -796,7 +801,7 @@ App::get('/v1/storage/buckets/:bucketId/files') }); App::get('/v1/storage/buckets/:bucketId/files/:fileId') - ->alias('/v1/storage/files/:fileId', ['bucketId' => 'default']) + ->alias('/v1/storage/files/:fileId') ->desc('Get file') ->groups(['api', 'storage']) ->label('scope', 'files.read') @@ -844,7 +849,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId') }); App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') - ->alias('/v1/storage/files/:fileId/preview', ['bucketId' => 'default']) + ->alias('/v1/storage/files/:fileId/preview') ->desc('Get file preview') ->groups(['api', 'storage']) ->label('scope', 'files.read') @@ -1017,7 +1022,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') }); App::get('/v1/storage/buckets/:bucketId/files/:fileId/download') - ->alias('/v1/storage/files/:fileId/download', ['bucketId' => 'default']) + ->alias('/v1/storage/files/:fileId/download') ->desc('Get file for download') ->groups(['api', 'storage']) ->label('scope', 'files.read') @@ -1158,7 +1163,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download') }); App::get('/v1/storage/buckets/:bucketId/files/:fileId/view') - ->alias('/v1/storage/files/:fileId/view', ['bucketId' => 'default']) + ->alias('/v1/storage/files/:fileId/view') ->desc('Get file for view') ->groups(['api', 'storage']) ->label('scope', 'files.read') @@ -1465,7 +1470,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push') }); App::put('/v1/storage/buckets/:bucketId/files/:fileId') - ->alias('/v1/storage/files/:fileId', ['bucketId' => 'default']) + ->alias('/v1/storage/files/:fileId') ->desc('Update file') ->groups(['api', 'storage']) ->label('scope', 'files.write') @@ -1555,10 +1560,14 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId') $file->setAttribute('name', $name); } - if ($fileSecurity && !$valid) { - $file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file); - } else { - $file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file)); + try { + if ($fileSecurity && !$valid) { + $file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file); + } else { + $file = Authorization::skip(fn() => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file)); + } + } catch (NotFoundException) { + throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } $queueForEvents @@ -1641,10 +1650,14 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId') ->setResource('file/' . $fileId) ; - if ($fileSecurity && !$valid) { - $deleted = $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId); - } else { - $deleted = Authorization::skip(fn () => $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId)); + try { + if ($fileSecurity && !$valid) { + $deleted = $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId); + } else { + $deleted = Authorization::skip(fn() => $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId)); + } + } catch (NotFoundException) { + throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } if (!$deleted) { From de2ce16f10b165734ed36633f45a639604ddfbcb Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 4 Nov 2024 21:49:20 +1300 Subject: [PATCH 17/33] Fix non-variable passed by reference --- app/controllers/api/functions.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 8815611021..544abec317 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -2642,9 +2642,11 @@ App::get('/v1/functions/templates/:templateId') ->action(function (string $templateId, Response $response) { $templates = Config::getParam('function-templates', []); - $template = array_shift(\array_filter($templates, function ($template) use ($templateId) { + $filtered = \array_filter($templates, function ($template) use ($templateId) { return $template['id'] === $templateId; - })); + }); + + $template = array_shift($filtered); if (empty($template)) { throw new Exception(Exception::FUNCTION_TEMPLATE_NOT_FOUND); From 6bba134650c2aeb3015db3963dec24f9d1276365 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 4 Nov 2024 21:50:22 +1300 Subject: [PATCH 18/33] Set min/max for datetime validator --- app/controllers/api/databases.php | 4 ++-- app/controllers/api/functions.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 7c10662946..4a60fbc257 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -1607,7 +1607,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/dateti ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') ->param('key', '', new Key(), 'Attribute Key.') ->param('required', null, new Boolean(), 'Is attribute required?') - ->param('default', null, new DatetimeValidator(), 'Default value for the attribute in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Cannot be set when attribute is required.', true) + ->param('default', null, fn(Database $dbForProject) => new DatetimeValidator($dbForProject->getAdapter()->getMinDateTime(), $dbForProject->getAdapter()->getMinDateTime()), 'Default value for the attribute in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Cannot be set when attribute is required.', true, ['dbForProject']) ->param('array', false, new Boolean(), 'Is attribute an array?', true) ->inject('response') ->inject('dbForProject') @@ -2295,7 +2295,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/datet ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') ->param('key', '', new Key(), 'Attribute Key.') ->param('required', null, new Boolean(), 'Is attribute required?') - ->param('default', null, new Nullable(new DatetimeValidator()), 'Default value for attribute when not provided. Cannot be set when attribute is required.') + ->param('default', null, fn(Database $dbForProject) => new Nullable(new DatetimeValidator($dbForProject->getAdapter()->getMinDateTime(), $dbForProject->getAdapter()->getMinDateTime())), 'Default value for attribute when not provided. Cannot be set when attribute is required.', injections: ['dbForProject']) ->param('newKey', null, new Key(), 'New attribute key.', true) ->inject('response') ->inject('dbForProject') diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 544abec317..0ca73ee6ac 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -1753,7 +1753,7 @@ App::post('/v1/functions/:functionId/executions') ->param('path', '/', new Text(2048), 'HTTP path of execution. Path can include query params. Default value is /', true) ->param('method', 'POST', new Whitelist(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], true), 'HTTP method of execution. Default value is GET.', true) ->param('headers', [], new AnyOf([new Assoc(), new Text(65535)], AnyOf::TYPE_MIXED), 'HTTP headers of execution. Defaults to empty.', true) - ->param('scheduledAt', null, new DatetimeValidator(true, DateTimeValidator::PRECISION_MINUTES, 60), 'Scheduled execution time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future with precision in minutes.', true) + ->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true, precision: DateTimeValidator::PRECISION_MINUTES, offset: 60), 'Scheduled execution time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future with precision in minutes.', true) ->inject('response') ->inject('request') ->inject('project') From a00671fe2949bab208dfc7ad327cf875fe19ab65 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 4 Nov 2024 21:53:00 +1300 Subject: [PATCH 19/33] Lint --- app/controllers/api/databases.php | 4 ++-- app/controllers/api/storage.php | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 4a60fbc257..c0c7a09938 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -1607,7 +1607,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/dateti ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') ->param('key', '', new Key(), 'Attribute Key.') ->param('required', null, new Boolean(), 'Is attribute required?') - ->param('default', null, fn(Database $dbForProject) => new DatetimeValidator($dbForProject->getAdapter()->getMinDateTime(), $dbForProject->getAdapter()->getMinDateTime()), 'Default value for the attribute in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Cannot be set when attribute is required.', true, ['dbForProject']) + ->param('default', null, fn (Database $dbForProject) => new DatetimeValidator($dbForProject->getAdapter()->getMinDateTime(), $dbForProject->getAdapter()->getMinDateTime()), 'Default value for the attribute in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Cannot be set when attribute is required.', true, ['dbForProject']) ->param('array', false, new Boolean(), 'Is attribute an array?', true) ->inject('response') ->inject('dbForProject') @@ -2295,7 +2295,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/datet ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') ->param('key', '', new Key(), 'Attribute Key.') ->param('required', null, new Boolean(), 'Is attribute required?') - ->param('default', null, fn(Database $dbForProject) => new Nullable(new DatetimeValidator($dbForProject->getAdapter()->getMinDateTime(), $dbForProject->getAdapter()->getMinDateTime())), 'Default value for attribute when not provided. Cannot be set when attribute is required.', injections: ['dbForProject']) + ->param('default', null, fn (Database $dbForProject) => new Nullable(new DatetimeValidator($dbForProject->getAdapter()->getMinDateTime(), $dbForProject->getAdapter()->getMinDateTime())), 'Default value for attribute when not provided. Cannot be set when attribute is required.', injections: ['dbForProject']) ->param('newKey', null, new Key(), 'New attribute key.', true) ->inject('response') ->inject('dbForProject') diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 6b8248f101..dda39a0db0 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -787,8 +787,8 @@ App::get('/v1/storage/buckets/:bucketId/files') $files = $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries); $total = $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT); } else { - $files = Authorization::skip(fn() => $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries)); - $total = Authorization::skip(fn() => $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT)); + $files = Authorization::skip(fn () => $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries)); + $total = Authorization::skip(fn () => $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT)); } } catch (NotFoundException) { throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); @@ -1564,7 +1564,7 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId') if ($fileSecurity && !$valid) { $file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file); } else { - $file = Authorization::skip(fn() => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file)); + $file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file)); } } catch (NotFoundException) { throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); @@ -1654,7 +1654,7 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId') if ($fileSecurity && !$valid) { $deleted = $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId); } else { - $deleted = Authorization::skip(fn() => $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId)); + $deleted = Authorization::skip(fn () => $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId)); } } catch (NotFoundException) { throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); From 4489d5f77f1a95cc3e3b9e4b875110d62f01ac3e Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 4 Nov 2024 22:13:38 +1300 Subject: [PATCH 20/33] Fix validator --- app/controllers/api/databases.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index c0c7a09938..84a311e342 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -1607,7 +1607,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/dateti ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') ->param('key', '', new Key(), 'Attribute Key.') ->param('required', null, new Boolean(), 'Is attribute required?') - ->param('default', null, fn (Database $dbForProject) => new DatetimeValidator($dbForProject->getAdapter()->getMinDateTime(), $dbForProject->getAdapter()->getMinDateTime()), 'Default value for the attribute in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Cannot be set when attribute is required.', true, ['dbForProject']) + ->param('default', null, fn (Database $dbForProject) => new DatetimeValidator($dbForProject->getAdapter()->getMinDateTime(), $dbForProject->getAdapter()->getMaxDateTime()), 'Default value for the attribute in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Cannot be set when attribute is required.', true, ['dbForProject']) ->param('array', false, new Boolean(), 'Is attribute an array?', true) ->inject('response') ->inject('dbForProject') @@ -2295,7 +2295,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/datet ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') ->param('key', '', new Key(), 'Attribute Key.') ->param('required', null, new Boolean(), 'Is attribute required?') - ->param('default', null, fn (Database $dbForProject) => new Nullable(new DatetimeValidator($dbForProject->getAdapter()->getMinDateTime(), $dbForProject->getAdapter()->getMinDateTime())), 'Default value for attribute when not provided. Cannot be set when attribute is required.', injections: ['dbForProject']) + ->param('default', null, fn (Database $dbForProject) => new Nullable(new DatetimeValidator($dbForProject->getAdapter()->getMinDateTime(), $dbForProject->getAdapter()->getMaxDateTime())), 'Default value for attribute when not provided. Cannot be set when attribute is required.', injections: ['dbForProject']) ->param('newKey', null, new Key(), 'New attribute key.', true) ->inject('response') ->inject('dbForProject') @@ -3451,7 +3451,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen }); App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId') - ->alias('/v1/database/collections/:collectionId/documents/:documentId', ['databaseId' => 'default']) + ->alias('/v1/database/collections/:collectionId/documents/:documentId') ->desc('Update document') ->groups(['api', 'database']) ->label('event', 'databases.[databaseId].collections.[collectionId].documents.[documentId].update') 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 21/33] 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 22/33] 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 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 23/33] 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 24/33] 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 25/33] 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 26/33] 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 27/33] 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 28/33] 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 29/33] 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 30/33] 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 31/33] 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 32/33] 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 b410b17ba61e451c0430c7fe5b3f28bec4236c95 Mon Sep 17 00:00:00 2001 From: Fabian Gruber Date: Thu, 31 Oct 2024 13:12:03 +0100 Subject: [PATCH 33/33] 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); }