From 6de577191b8bb231f8279ff60a0e64ddf480ebb4 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Tue, 29 Mar 2022 11:30:57 +0300 Subject: [PATCH 01/35] feat: event model parsing --- src/Appwrite/Event/Event.php | 177 +++++++++++++++++++++++++++------ tests/unit/Event/EventTest.php | 69 +++++++++++-- 2 files changed, 202 insertions(+), 44 deletions(-) diff --git a/src/Appwrite/Event/Event.php b/src/Appwrite/Event/Event.php index 2bd73248c3..3985a807f4 100644 --- a/src/Appwrite/Event/Event.php +++ b/src/Appwrite/Event/Event.php @@ -2,12 +2,13 @@ namespace Appwrite\Event; +use Exception; +use InvalidArgumentException; use Resque; class Event { - - const DATABASE_QUEUE_NAME= 'v1-database'; + const DATABASE_QUEUE_NAME = 'v1-database'; const DATABASE_CLASS_NAME = 'DatabaseV1'; const DELETE_QUEUE_NAME = 'v1-deletes'; @@ -33,27 +34,15 @@ class Event const BUILDS_QUEUE_NAME = 'v1-builds'; const BUILDS_CLASS_NAME = 'BuildsV1'; - - /** - * @var string - */ - protected $queue = ''; + + protected string $queue = ''; + protected string $class = ''; + protected array $params = []; /** - * @var string - */ - protected $class = ''; - - /** - * @var array - */ - protected $params = []; - - /** - * Event constructor. - * * @param string $queue * @param string $class + * @return void */ public function __construct(string $queue, string $class) { @@ -62,8 +51,10 @@ class Event } /** + * Set queue used for this event. + * * @param string $queue - * return $this + * @return Event */ public function setQueue(string $queue): self { @@ -72,16 +63,19 @@ class Event } /** + * Get queue used for this event. + * * @return string */ - public function getQueue() + public function getQueue(): string { return $this->queue; } /** + * Set class used for this event. * @param string $class - * return $this + * @return Event */ public function setClass(string $class): self { @@ -90,20 +84,23 @@ class Event } /** + * Get class used for this event. + * * @return string */ - public function getClass() + public function getClass(): string { return $this->class; } /** - * @param string $key - * @param mixed $value + * Set param of event. * - * @return $this + * @param string $key + * @param mixed $value + * @return Event */ - public function setParam(string $key, $value): self + public function setParam(string $key, mixed $value): self { $this->params[$key] = $value; @@ -111,29 +108,143 @@ class Event } /** - * @param string $key + * Get param of event. * - * @return mixed|null + * @param string $key + * @return mixed */ - public function getParam(string $key) + public function getParam(string $key): mixed { - return (isset($this->params[$key])) ? $this->params[$key] : null; + return $this->params[$key] ?? null; } /** * Execute Event. + * + * @return Event + * @throws InvalidArgumentException */ - public function trigger(): void + public function trigger(): self { Resque::enqueue($this->queue, $this->class, $this->params); - $this->reset(); + return $this->reset(); } + /** + * Resets event. + * + * @return Event + */ public function reset(): self { $this->params = []; return $this; } + + static function generateEvents(string $pattern, array $params = []): array + { + $parts = \explode('.', $pattern); + $count = \count($parts); + + if ($count < 2 || $count > 6) { + throw new Exception("Patten incorrect."); + } + + /** + * Identify all sestions of the pattern. + */ + $type = $parts[0]; + $action = match ($count) { + 2 => $parts[1], + 3, 4 => $parts[2], + 5, 6 => $parts[4] + }; + + if ($count > 4) { + $subType = $parts[2]; + $subResource = $parts[3]; + if ($count === 6) { + $attribute = $parts[5]; + } + } + if ($count > 2) { + $resource = $parts[1]; + if ($count === 4) { + $attribute = $parts[3]; + } + } + + $paramKeys = \array_keys($params); + $paramValues = \array_values($params); + + $patterns = []; + $resource ??= false; + $subResource ??= false; + $attribute ??= false; + + if (empty($params) && ($type ?? false) && !$resource) { + return [$pattern]; + } + + if ($resource && !\in_array(\trim($resource, '[]'), $paramKeys)) { + throw new InvalidArgumentException("{$resource} is missing from the params."); + } + + if ($subResource && !\in_array(\trim($subResource, '[]'), $paramKeys)) { + throw new InvalidArgumentException("{$subResource} is missing from the params."); + } + + /** + * Create all possible patterns including placeholders. + */ + if ($action) { + if ($subResource) { + if ($attribute) { + $patterns[] = \implode('.', [$type, $resource, $subType, $subResource, $action, $attribute]); + } + $patterns[] = \implode('.', [$type, $resource, $subType, $subResource, $action]); + $patterns[] = \implode('.', [$type, $resource, $subType, $subResource]); + } else { + if ($attribute) { + $patterns[] = \implode('.', [$type, $resource, $action, $attribute]); + } + $patterns[] = \implode('.', [$type, $resource, $action]); + $patterns[] = \implode('.', [$type, $resource]); + } + } + if ($subResource) { + $patterns[] = \implode('.', [$type, $resource, $subType, $subResource]); + } + + /** + * Removes all duplicates. + */ + $patterns = \array_unique($patterns); + + /** + * Set all possible values of the patterns and replace placeholders. + */ + $events = []; + foreach ($patterns as $eventPattern) { + $events[] = \str_replace($paramKeys, $paramValues, $eventPattern); + $events[] = \str_replace($paramKeys, '*', $eventPattern); + foreach ($paramKeys as $key) { + foreach ($paramKeys as $current) { + if ($current === $key) continue; + + $filtered = \array_filter($paramKeys, fn(string $k) => $k === $current); + $events[] = \str_replace($paramKeys, $paramValues, \str_replace($filtered, '*', $eventPattern)); + } + } + } + + /** + * Remove [] from the events. + */ + $events = \array_map(fn (string $event) => \str_replace(['[', ']'], '', $event), $events); + + return $events; + } } diff --git a/tests/unit/Event/EventTest.php b/tests/unit/Event/EventTest.php index 58d7a1c045..52afc58d5c 100644 --- a/tests/unit/Event/EventTest.php +++ b/tests/unit/Event/EventTest.php @@ -3,6 +3,7 @@ namespace Appwrite\Tests; use Appwrite\Event\Event; +use InvalidArgumentException; use PHPUnit\Framework\TestCase; use Utopia\App; @@ -12,7 +13,7 @@ class EventTest extends TestCase * @var Event */ protected $object = null; - + /** * @var string */ @@ -22,8 +23,8 @@ class EventTest extends TestCase { $redisHost = App::getEnv('_APP_REDIS_HOST', ''); $redisPort = App::getEnv('_APP_REDIS_PORT', ''); - \Resque::setBackend($redisHost.':'.$redisPort); - + \Resque::setBackend($redisHost . ':' . $redisPort); + $this->queue = 'v1-tests' . uniqid(); $this->object = new Event($this->queue, 'TestsV1'); } @@ -37,9 +38,9 @@ class EventTest extends TestCase $this->assertEquals($this->queue, $this->object->getQueue()); $this->object->setQueue('demo'); - + $this->assertEquals('demo', $this->object->getQueue()); - + $this->object->setQueue($this->queue); } @@ -48,9 +49,9 @@ class EventTest extends TestCase $this->assertEquals('TestsV1', $this->object->getClass()); $this->object->setClass('TestsV2'); - + $this->assertEquals('TestsV2', $this->object->getClass()); - + $this->object->setClass('TestsV1'); } @@ -58,8 +59,7 @@ class EventTest extends TestCase { $this->object ->setParam('eventKey1', 'eventValue1') - ->setParam('eventKey2', 'eventValue2') - ; + ->setParam('eventKey2', 'eventValue2'); $this->object->trigger(); @@ -73,8 +73,7 @@ class EventTest extends TestCase { $this->object ->setParam('eventKey1', 'eventValue1') - ->setParam('eventKey2', 'eventValue2') - ; + ->setParam('eventKey2', 'eventValue2'); $this->assertEquals('eventValue1', $this->object->getParam('eventKey1')); $this->assertEquals('eventValue2', $this->object->getParam('eventKey2')); @@ -85,4 +84,52 @@ class EventTest extends TestCase $this->assertEquals(null, $this->object->getParam('eventKey2')); $this->assertEquals(null, $this->object->getParam('eventKey3')); } + + public function testGenerateEvents() + { + $event = Event::generateEvents('users.create'); + $this->assertCount(1, $event); + $this->assertContains('users.create', $event); + + $event = Event::generateEvents('users.[userId].update.email', [ + 'userId' => 'torsten' + ]); + $this->assertCount(6, $event); + $this->assertContains('users.torsten.update.email', $event); + $this->assertContains('users.torsten.update', $event); + $this->assertContains('users.torsten', $event); + $this->assertContains('users.*.update.email', $event); + $this->assertContains('users.*.update', $event); + $this->assertContains('users.*', $event); + + $event = Event::generateEvents('collections.[collectionId].documents.[documentId].create', [ + 'collectionId' => 'chapters', + 'documentId' => 'prolog', + ]); + $this->assertCount(8, $event); + $this->assertContains('collections.chapters.documents.prolog.create', $event); + $this->assertContains('collections.chapters.documents.prolog', $event); + $this->assertContains('collections.chapters.documents.*.create', $event); + $this->assertContains('collections.chapters.documents.*', $event); + $this->assertContains('collections.*.documents.prolog.create', $event); + $this->assertContains('collections.*.documents.prolog', $event); + $this->assertContains('collections.*.documents.*.create', $event); + $this->assertContains('collections.*.documents.*', $event); + + try { + $event = Event::generateEvents('collections.[collectionId].documents.[documentId].create', [ + 'collectionId' => 'chapters' + ]); + $this->fail(); + } catch (\Throwable $th) { + $this->assertInstanceOf(InvalidArgumentException::class, $th, 'An invalid exception was thrown'); + } + + try { + $event = Event::generateEvents('collections.[collectionId].documents.[documentId].create'); + $this->fail(); + } catch (\Throwable $th) { + $this->assertInstanceOf(InvalidArgumentException::class, $th, 'An invalid exception was thrown'); + } + } } From 2f9b9445dd1ff69f4d1a2a1d3e161a3278c3a198 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Mon, 4 Apr 2022 08:30:07 +0200 Subject: [PATCH 02/35] sync with devices --- app/config/events.php | 6 + app/controllers/api/account.php | 306 +++++++++++------- app/controllers/api/projects.php | 5 +- app/controllers/api/users.php | 129 +++++--- app/controllers/shared/api.php | 56 ++-- app/realtime.php | 3 +- app/workers/audits.php | 33 +- app/workers/mails.php | 23 +- app/workers/webhooks.php | 107 +++--- src/Appwrite/Event/Event.php | 159 +++++++-- src/Appwrite/Event/Validator/Event.php | 172 ++++++++++ tests/e2e/Scopes/ProjectCustom.php | 53 +-- tests/e2e/Services/Account/AccountBase.php | 8 +- tests/unit/Event/EventTest.php | 11 +- .../Event/Validator/EventValidatorTest.php | 51 +++ 15 files changed, 785 insertions(+), 337 deletions(-) create mode 100644 src/Appwrite/Event/Validator/Event.php create mode 100644 tests/unit/Event/Validator/EventValidatorTest.php diff --git a/app/config/events.php b/app/config/events.php index bfb70e2180..157e312888 100644 --- a/app/config/events.php +++ b/app/config/events.php @@ -282,4 +282,10 @@ return [ 'model' => Response::MODEL_MEMBERSHIP, 'note' => 'version >= 0.7', ], + + 'users.*' => [ + 'description' => 'This event triggers when a team memberships is deleted.', + 'model' => Response::MODEL_MEMBERSHIP, + 'note' => 'version >= 0.7', + ], ]; diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index f0c10dd5ea..2c805618fc 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -23,7 +23,6 @@ use Utopia\Database\Validator\UID; use Appwrite\Extend\Exception; use Utopia\Validator\ArrayList; use Utopia\Validator\Assoc; -use Utopia\Validator\Boolean; use Utopia\Validator\Range; use Utopia\Validator\Text; use Utopia\Validator\WhiteList; @@ -34,7 +33,7 @@ $oauthDefaultFailure = '/v1/auth/oauth2/failure'; App::post('/v1/account') ->desc('Create Account') ->groups(['api', 'account', 'auth']) - ->label('event', 'account.create') + ->label('event', 'users.[userId].create') ->label('scope', 'public') ->label('auth.type', 'emailPassword') ->label('sdk.auth', []) @@ -55,13 +54,15 @@ App::post('/v1/account') ->inject('dbForProject') ->inject('audits') ->inject('usage') - ->action(function ($userId, $email, $password, $name, $request, $response, $project, $dbForProject, $audits, $usage) { + ->inject('events') + ->action(function ($userId, $email, $password, $name, $request, $response, $project, $dbForProject, $audits, $usage, $events) { /** @var Appwrite\Utopia\Request $request */ /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Document $project */ /** @var Utopia\Database\Database $dbForProject */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Stats\Stats $usage */ + /** @var Appwrite\Event\Event $events */ $email = \strtolower($email); if ('console' === $project->getId()) { @@ -120,13 +121,19 @@ App::post('/v1/account') $audits ->setParam('userId', $user->getId()) - ->setParam('event', 'account.create') - ->setParam('resource', 'user/' . $user->getId()) + ->setUser($user) + ->setPayload(array_merge($audits->getPayload(), [ + 'resource' => 'user/'.$user->getId() + ])) ; $usage ->setParam('users.create', 1) ; + + $events + ->setParam('userId', $user->getId()) + ; $response->setStatusCode(Response::STATUS_CODE_CREATED); $response->dynamic($user, Response::MODEL_USER); }); @@ -134,7 +141,7 @@ App::post('/v1/account') App::post('/v1/account/sessions') ->desc('Create Account Session') ->groups(['api', 'account', 'auth']) - ->label('event', 'account.sessions.create') + ->label('event', 'users.[userId].sessions.[sessionId].create') ->label('scope', 'public') ->label('auth.type', 'emailPassword') ->label('sdk.auth', []) @@ -155,7 +162,8 @@ App::post('/v1/account/sessions') ->inject('geodb') ->inject('audits') ->inject('usage') - ->action(function ($email, $password, $request, $response, $dbForProject, $locale, $geodb, $audits, $usage) { + ->inject('events') + ->action(function ($email, $password, $request, $response, $dbForProject, $locale, $geodb, $audits, $usage, $events) { /** @var Appwrite\Utopia\Request $request */ /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ @@ -163,6 +171,7 @@ App::post('/v1/account/sessions') /** @var MaxMind\Db\Reader $geodb */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Stats\Stats $usage */ + /** @var Appwrite\Event\Event $events */ $email = \strtolower($email); $protocol = $request->getProtocol(); @@ -170,12 +179,6 @@ App::post('/v1/account/sessions') $profile = $dbForProject->findOne('users', [new Query('deleted', Query::TYPE_EQUAL, [false]), new Query('email', Query::TYPE_EQUAL, [$email])]); // Get user by email address if (!$profile || !Auth::passwordVerify($password, $profile->getAttribute('password'))) { - $audits - //->setParam('userId', $profile->getId()) - ->setParam('event', 'account.sessions.failed') - ->setParam('resource', 'user/'.($profile ? $profile->getId() : '')) - ; - throw new Exception('Invalid credentials', 401, Exception::USER_INVALID_CREDENTIALS); // Wrong password or username } @@ -213,10 +216,11 @@ App::post('/v1/account/sessions') $audits ->setParam('userId', $profile->getId()) - ->setParam('event', 'account.sessions.create') - ->setParam('resource', 'user/' . $profile->getId()) - ->setParam('userEmail', $profile->getAttribute('email', '')) - ->setParam('userName', $profile->getAttribute('name', '')) + ->setParam('sessionId', $session->getId()) + ->setUser($profile) + ->setPayload(array_merge($audits->getPayload(), [ + 'resource' => 'user/'.$profile->getId() + ])) ; if (!Config::getParam('domainVerification')) { @@ -243,6 +247,12 @@ App::post('/v1/account/sessions') ->setParam('users.sessions.create', 1) ->setParam('provider', 'email') ; + + $events + ->setParam('userId', $profile->getId()) + ->setParam('sessionId', $session->getId()) + ; + $response->dynamic($session, Response::MODEL_SESSION); }); @@ -365,7 +375,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') ->desc('OAuth2 Redirect') ->groups(['api', 'account']) ->label('error', __DIR__ . '/../../views/general/error.phtml') - ->label('event', 'account.sessions.create') + ->label('event', 'users.[userId].sessions.[sessionId].create') ->label('scope', 'public') ->label('abuse-limit', 50) ->label('abuse-key', 'ip:{ip}') @@ -390,6 +400,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') /** @var Utopia\Database\Database $dbForProject */ /** @var MaxMind\Db\Reader $geodb */ /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Event $events */ /** @var Appwrite\Stats\Stats $usage */ $protocol = $request->getProtocol(); @@ -567,18 +578,25 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $audits ->setParam('userId', $user->getId()) - ->setParam('event', 'account.sessions.create') - ->setParam('resource', 'user/' . $user->getId()) - ->setParam('data', ['provider' => $provider]) + ->setParam('sessionId', $session->getId()) + ->setUser($user) + ->setPayload(array_merge($audits->getPayload(), [ + 'resource' => 'user/'.$user->getId() + ])) ; - $events->setParam('eventData', $response->output($session, Response::MODEL_SESSION)); - $usage ->setParam('users.sessions.create', 1) ->setParam('projectId', $project->getId()) ->setParam('provider', 'oauth2-'.$provider) ; + + $events + ->setParam('userId', $user->getId()) + ->setParam('sessionId', $session->getId()) + ->setPayload($response->output($session, Response::MODEL_SESSION)) + ; + if (!Config::getParam('domainVerification')) { $response ->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)])) @@ -687,7 +705,6 @@ App::post('/v1/account/sessions/magic-url') ]))); $mails->setParam('event', 'users.create'); - $audits->setParam('event', 'users.create'); } $loginSecret = Auth::tokenGenerator(); @@ -723,12 +740,14 @@ App::post('/v1/account/sessions/magic-url') $url = Template::unParseURL($url); $mails - ->setParam('from', $project->getId()) - ->setParam('recipient', $user->getAttribute('email')) - ->setParam('url', $url) - ->setParam('locale', $locale->default) - ->setParam('project', $project->getAttribute('name', ['[APP-NAME]'])) - ->setParam('type', MAIL_TYPE_MAGIC_SESSION) + ->setPayload([ + 'from' => $project->getId(), + 'recipient' => $user->getAttribute('email'), + 'url' => $url, + 'locale' => $locale->default, + 'project' => $project->getAttribute('name', ['[APP-NAME]']), + 'type' => MAIL_TYPE_MAGIC_SESSION + ]) ->trigger() ; @@ -744,8 +763,10 @@ App::post('/v1/account/sessions/magic-url') ($isPrivilegedUser || $isAppUser) ? $loginSecret : ''); $audits - ->setParam('userId', $user->getId()) - ->setParam('resource', 'users/'.$user->getId()) + ->setUser($user) + ->setPayload(array_merge($audits->getPayload(), [ + 'resource' => 'user/'.$user->getId() + ])) ; $response @@ -758,7 +779,7 @@ App::put('/v1/account/sessions/magic-url') ->desc('Create Magic URL session (confirmation)') ->groups(['api', 'account']) ->label('scope', 'public') - ->label('event', 'account.sessions.create') + ->label('event', 'users.[userId].sessions.[sessionId].create') ->label('sdk.auth', []) ->label('sdk.namespace', 'account') ->label('sdk.method', 'updateMagicURLSession') @@ -776,7 +797,8 @@ App::put('/v1/account/sessions/magic-url') ->inject('locale') ->inject('geodb') ->inject('audits') - ->action(function ($userId, $secret, $request, $response, $dbForProject, $locale, $geodb, $audits) { + ->inject('events') + ->action(function ($userId, $secret, $request, $response, $dbForProject, $locale, $geodb, $audits, $events) { /** @var string $userId */ /** @var string $secret */ /** @var Appwrite\Utopia\Request $request */ @@ -785,6 +807,7 @@ App::put('/v1/account/sessions/magic-url') /** @var Utopia\Locale\Locale $locale */ /** @var MaxMind\Db\Reader $geodb */ /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Event $events */ $user = $dbForProject->getDocument('users', $userId); @@ -850,8 +873,15 @@ App::put('/v1/account/sessions/magic-url') $audits ->setParam('userId', $user->getId()) - ->setParam('event', 'account.sessions.create') - ->setParam('resource', 'users/'.$user->getId()) + ->setParam('sessionId', $session->getId()) + ->setPayload(array_merge($audits->getPayload(), [ + 'resource' => 'user/'.$user->getId() + ])) + ; + + $events + ->setParam('userId', $user->getId()) + ->setParam('sessionId', $session->getId()) ; if (!Config::getParam('domainVerification')) { @@ -883,7 +913,7 @@ App::put('/v1/account/sessions/magic-url') App::post('/v1/account/sessions/anonymous') ->desc('Create Anonymous Session') ->groups(['api', 'account', 'auth']) - ->label('event', 'account.sessions.create') + ->label('event', 'users.[userId].sessions.[sessionId].create') ->label('scope', 'public') ->label('auth.type', 'anonymous') ->label('sdk.auth', []) @@ -904,7 +934,8 @@ App::post('/v1/account/sessions/anonymous') ->inject('geodb') ->inject('audits') ->inject('usage') - ->action(function ($request, $response, $locale, $user, $project, $dbForProject, $geodb, $audits, $usage) { + ->inject('events') + ->action(function ($request, $response, $locale, $user, $project, $dbForProject, $geodb, $audits, $usage, $events) { /** @var Appwrite\Utopia\Request $request */ /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Locale\Locale $locale */ @@ -914,6 +945,7 @@ App::post('/v1/account/sessions/anonymous') /** @var MaxMind\Db\Reader $geodb */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Stats\Stats $usage */ + /** @var Appwrite\Stats\Stats $events */ $protocol = $request->getProtocol(); @@ -992,8 +1024,10 @@ App::post('/v1/account/sessions/anonymous') $audits ->setParam('userId', $user->getId()) - ->setParam('event', 'account.sessions.create') - ->setParam('resource', 'user/' . $user->getId()) + ->setParam('sessionId', $session->getId()) + ->setPayload(array_merge($audits->getPayload(), [ + 'resource' => 'user/'.$user->getId() + ])) ; $usage @@ -1001,6 +1035,11 @@ App::post('/v1/account/sessions/anonymous') ->setParam('provider', 'anonymous') ; + $events + ->setParam('userId', $user->getId()) + ->setParam('sessionId', $session->getId()) + ; + if (!Config::getParam('domainVerification')) { $response ->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)])) @@ -1196,26 +1235,8 @@ App::get('/v1/account/logs') /** @var Appwrite\Stats\Stats $usage */ $audit = new Audit($dbForProject); - $auditEvents = [ - 'account.create', - 'account.delete', - 'account.update.name', - 'account.update.email', - 'account.update.password', - 'account.update.prefs', - 'account.sessions.create', - 'account.sessions.update', - 'account.sessions.delete', - 'account.recovery.create', - 'account.recovery.update', - 'account.verification.create', - 'account.verification.update', - 'teams.membership.create', - 'teams.membership.update', - 'teams.membership.delete', - ]; - $logs = $audit->getLogsByUserAndEvents($user->getId(), $auditEvents, $limit, $offset); + $logs = $audit->getLogsByUser($user->getId(), $limit, $offset); $output = []; @@ -1249,7 +1270,7 @@ App::get('/v1/account/logs') ; $response->dynamic(new Document([ - 'total' => $audit->countLogsByUserAndEvents($user->getId(), $auditEvents), + 'total' => $audit->countLogsByUser($user->getId()), 'logs' => $output, ]), Response::MODEL_LOG_LIST); }); @@ -1309,7 +1330,7 @@ App::get('/v1/account/sessions/:sessionId') App::patch('/v1/account/name') ->desc('Update Account Name') ->groups(['api', 'account']) - ->label('event', 'account.update.name') + ->label('event', 'users.[userId].update.name') ->label('scope', 'account') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'account') @@ -1324,12 +1345,14 @@ App::patch('/v1/account/name') ->inject('dbForProject') ->inject('audits') ->inject('usage') - ->action(function ($name, $response, $user, $dbForProject, $audits, $usage) { + ->inject('events') + ->action(function ($name, $response, $user, $dbForProject, $audits, $usage, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Document $user */ /** @var Utopia\Database\Database $dbForProject */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Stats\Stats $usage */ + /** @var Appwrite\Stats\Stats $events */ $user = $dbForProject->updateDocument('users', $user->getId(), $user ->setAttribute('name', $name) @@ -1338,21 +1361,27 @@ App::patch('/v1/account/name') $audits ->setParam('userId', $user->getId()) - ->setParam('event', 'account.update.name') - ->setParam('resource', 'user/' . $user->getId()) + ->setUser($user) + ->setPayload(array_merge($audits->getPayload(), [ + 'resource' => 'user/'.$user->getId() + ])) ; $usage ->setParam('users.update', 1) ; + $events + ->setParam('userId', $user->getId()) + ; + $response->dynamic($user, Response::MODEL_USER); }); App::patch('/v1/account/password') ->desc('Update Account Password') ->groups(['api', 'account']) - ->label('event', 'account.update.password') + ->label('event', 'users.[userId].update.password') ->label('scope', 'account') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'account') @@ -1368,12 +1397,14 @@ App::patch('/v1/account/password') ->inject('dbForProject') ->inject('audits') ->inject('usage') - ->action(function ($password, $oldPassword, $response, $user, $dbForProject, $audits, $usage) { + ->inject('events') + ->action(function ($password, $oldPassword, $response, $user, $dbForProject, $audits, $usage, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Document $user */ /** @var Utopia\Database\Database $dbForProject */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Stats\Stats $usage */ + /** @var Appwrite\Stats\Stats $events */ // Check old password only if its an existing user. if ($user->getAttribute('passwordUpdate') !== 0 && !Auth::passwordVerify($oldPassword, $user->getAttribute('password'))) { // Double check user password @@ -1387,20 +1418,27 @@ App::patch('/v1/account/password') $audits ->setParam('userId', $user->getId()) - ->setParam('event', 'account.update.password') - ->setParam('resource', 'user/' . $user->getId()) + ->setUser($user) + ->setPayload(array_merge($audits->getPayload(), [ + 'resource' => 'user/'.$user->getId() + ])) ; $usage ->setParam('users.update', 1) ; + + $events + ->setParam('userId', $user->getId()) + ; + $response->dynamic($user, Response::MODEL_USER); }); App::patch('/v1/account/email') ->desc('Update Account Email') ->groups(['api', 'account']) - ->label('event', 'account.update.email') + ->label('event', 'users.[userId].update.email') ->label('scope', 'account') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'account') @@ -1416,12 +1454,14 @@ App::patch('/v1/account/email') ->inject('dbForProject') ->inject('audits') ->inject('usage') - ->action(function ($email, $password, $response, $user, $dbForProject, $audits, $usage) { + ->inject('events') + ->action(function ($email, $password, $response, $user, $dbForProject, $audits, $usage, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Document $user */ /** @var Utopia\Database\Database $dbForProject */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Stats\Stats $usage */ + /** @var Appwrite\Stats\Stats $events */ $isAnonymousUser = is_null($user->getAttribute('email')) && is_null($user->getAttribute('password')); // Check if request is from an anonymous account for converting @@ -1452,20 +1492,27 @@ App::patch('/v1/account/email') $audits ->setParam('userId', $user->getId()) - ->setParam('event', 'account.update.email') - ->setParam('resource', 'user/' . $user->getId()) + ->setUser($user) + ->setPayload(array_merge($audits->getPayload(), [ + 'resource' => 'user/'.$user->getId() + ])) ; $usage ->setParam('users.update', 1) ; + + $events + ->setParam('userId', $user->getId()) + ; + $response->dynamic($user, Response::MODEL_USER); }); App::patch('/v1/account/prefs') ->desc('Update Account Preferences') ->groups(['api', 'account']) - ->label('event', 'account.update.prefs') + ->label('event', 'users.[userId].update.prefs') ->label('scope', 'account') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'account') @@ -1480,30 +1527,39 @@ App::patch('/v1/account/prefs') ->inject('dbForProject') ->inject('audits') ->inject('usage') - ->action(function ($prefs, $response, $user, $dbForProject, $audits, $usage) { + ->inject('events') + ->action(function ($prefs, $response, $user, $dbForProject, $audits, $usage, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Document $user */ /** @var Utopia\Database\Database $dbForProject */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Stats\Stats $usage */ + /** @var Appwrite\Event\Event $events */ $user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('prefs', $prefs)); $audits - ->setParam('event', 'account.update.prefs') - ->setParam('resource', 'user/' . $user->getId()) + ->setParam('userId', $user->getId()) + ->setPayload(array_merge($audits->getPayload(), [ + 'resource' => 'user/'.$user->getId() + ])) ; $usage ->setParam('users.update', 1) ; + + $events + ->setParam('userId', $user->getId()) + ; + $response->dynamic($user, Response::MODEL_USER); }); App::delete('/v1/account') ->desc('Delete Account') ->groups(['api', 'account']) - ->label('event', 'account.delete') + ->label('event', 'users.[userId].delete') ->label('scope', 'account') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'account') @@ -1534,11 +1590,11 @@ App::delete('/v1/account') // TODO delete all tokens or only current session? // TODO delete all user data according to GDPR. Make sure everything is backed up and backups are deleted later - /* - * Data to delete - * * Tokens - * * Memberships - */ + /** + * Data to delete + * * Tokens + * * Memberships + */ $audits ->setParam('userId', $user->getId()) @@ -1548,7 +1604,8 @@ App::delete('/v1/account') ; $events - ->setParam('eventData', $response->output($user, Response::MODEL_USER)) + ->setParam('userId', $user->getId()) + ->setPayload($response->output($user, Response::MODEL_USER)) ; if (!Config::getParam('domainVerification')) { @@ -1571,7 +1628,7 @@ App::delete('/v1/account/sessions/:sessionId') ->desc('Delete Account Session') ->groups(['api', 'account']) ->label('scope', 'account') - ->label('event', 'account.sessions.delete') + ->label('event', 'users.[userId].sessions.[sessionId].delete') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'account') ->label('sdk.method', 'deleteSession') @@ -1640,7 +1697,9 @@ App::delete('/v1/account/sessions/:sessionId') $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('sessions', $sessions)); $events - ->setParam('eventData', $response->output($session, Response::MODEL_SESSION)) + ->setPayload($response->output($session, Response::MODEL_SESSION)) + ->setParam('userId', $user->getId()) + ->setParam('sessionId', $session->getId()) ; $usage @@ -1658,7 +1717,7 @@ App::patch('/v1/account/sessions/:sessionId') ->desc('Update Session (Refresh Tokens)') ->groups(['api', 'account']) ->label('scope', 'account') - ->label('event', 'account.sessions.update') + ->label('event', 'users.[userId].sessions.[sessionId].update') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'account') ->label('sdk.method', 'updateSession') @@ -1741,7 +1800,9 @@ App::patch('/v1/account/sessions/:sessionId') ; $events - ->setParam('eventData', $response->output($session, Response::MODEL_SESSION)) + ->setPayload($response->output($session, Response::MODEL_SESSION)) + ->setParam('userId', $user->getId()) + ->setParam('sessionId', $session->getId()) ; $usage @@ -1760,7 +1821,7 @@ App::delete('/v1/account/sessions') ->desc('Delete All Account Sessions') ->groups(['api', 'account']) ->label('scope', 'account') - ->label('event', 'account.sessions.delete') + ->label('event', 'users.[userId].sessions.[sessionId].delete') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'account') ->label('sdk.method', 'deleteSessions') @@ -1823,10 +1884,12 @@ App::delete('/v1/account/sessions') $numOfSessions = count($sessions); $events - ->setParam('eventData', $response->output(new Document([ + ->setPayload($response->output(new Document([ 'sessions' => $sessions, 'total' => $numOfSessions, ]), Response::MODEL_SESSION_LIST)) + ->setParam('userId', $user->getId()) + ->setParam('sessionId', $session->getId()) ; $usage @@ -1840,7 +1903,7 @@ App::post('/v1/account/recovery') ->desc('Create Password Recovery') ->groups(['api', 'account']) ->label('scope', 'public') - ->label('event', 'account.recovery.create') + ->label('event', 'users.[userId].recovery.[tokenId].create') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'account') ->label('sdk.method', 'createRecovery') @@ -1915,14 +1978,15 @@ App::post('/v1/account/recovery') $url = Template::unParseURL($url); $mails - ->setParam('event', 'account.recovery.create') - ->setParam('from', $project->getId()) - ->setParam('recipient', $profile->getAttribute('email', '')) - ->setParam('name', $profile->getAttribute('name', '')) - ->setParam('url', $url) - ->setParam('locale', $locale->default) - ->setParam('project', $project->getAttribute('name', ['[APP-NAME]'])) - ->setParam('type', MAIL_TYPE_RECOVERY) + ->setPayload([ + 'from' => $project->getId(), + 'recipient' => $profile->getAttribute('email', ''), + 'name' => $profile->getAttribute('name', ''), + 'url' => $url, + 'locale' => $locale->default, + 'project' => $project->getAttribute('name', ['[APP-NAME]']), + 'type' => MAIL_TYPE_RECOVERY + ]) ->trigger(); ; @@ -1931,6 +1995,8 @@ App::post('/v1/account/recovery') $response->output($recovery->setAttribute('secret', $secret), Response::MODEL_TOKEN )) + ->setParam('userId', $profile->getId()) + ->setParam('tokenId', $recovery->getId()) ; $recovery // Hide secret for clients, sp @@ -1954,7 +2020,7 @@ App::put('/v1/account/recovery') ->desc('Create Password Recovery (confirmation)') ->groups(['api', 'account']) ->label('scope', 'public') - ->label('event', 'account.recovery.update') + ->label('event', 'users.[userId].recovery.[tokenId].update') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'account') ->label('sdk.method', 'updateRecovery') @@ -1972,11 +2038,13 @@ App::put('/v1/account/recovery') ->inject('dbForProject') ->inject('audits') ->inject('usage') - ->action(function ($userId, $secret, $password, $passwordAgain, $response, $dbForProject, $audits, $usage) { + ->inject('events') + ->action(function ($userId, $secret, $password, $passwordAgain, $response, $dbForProject, $audits, $usage, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Stats\Stats $usage */ + /** @var Appwrite\Event\Event $events */ if ($password !== $passwordAgain) { throw new Exception('Passwords must match', 400, Exception::USER_PASSWORD_MISMATCH); @@ -2025,6 +2093,11 @@ App::put('/v1/account/recovery') $usage ->setParam('users.update', 1) ; + + $events + ->setParam('userId', $profile->getId()) + ->setParam('tokenId', $recovery->getId()) + ; $response->dynamic($recovery, Response::MODEL_TOKEN); }); @@ -2032,7 +2105,7 @@ App::post('/v1/account/verification') ->desc('Create Email Verification') ->groups(['api', 'account']) ->label('scope', 'account') - ->label('event', 'account.verification.create') + ->label('event', 'users.[userId].verification.[tokenId].create') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'account') ->label('sdk.method', 'createVerification') @@ -2098,14 +2171,15 @@ App::post('/v1/account/verification') $url = Template::unParseURL($url); $mails - ->setParam('event', 'account.verification.create') - ->setParam('from', $project->getId()) - ->setParam('recipient', $user->getAttribute('email')) - ->setParam('name', $user->getAttribute('name')) - ->setParam('url', $url) - ->setParam('locale', $locale->default) - ->setParam('project', $project->getAttribute('name', ['[APP-NAME]'])) - ->setParam('type', MAIL_TYPE_VERIFICATION) + ->setPayload([ + 'from' => $project->getId(), + 'recipient' => $user->getAttribute('email'), + 'name' => $user->getAttribute('name'), + 'url' => $url, + 'locale' => $locale->default, + 'project' => $project->getAttribute('name', ['[APP-NAME]']), + 'type' => MAIL_TYPE_VERIFICATION + ]) ->trigger() ; @@ -2114,6 +2188,8 @@ App::post('/v1/account/verification') $response->output($verification->setAttribute('secret', $verificationSecret), Response::MODEL_TOKEN )) + ->setParam('userId', $user->getId()) + ->setParam('tokenId', $verification->getId()) ; $verification // Hide secret for clients, sp @@ -2137,7 +2213,7 @@ App::put('/v1/account/verification') ->desc('Create Email Verification (confirmation)') ->groups(['api', 'account']) ->label('scope', 'public') - ->label('event', 'account.verification.update') + ->label('event', 'users.[userId].verification.[tokenId].update') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'account') ->label('sdk.method', 'updateVerification') @@ -2154,12 +2230,14 @@ App::put('/v1/account/verification') ->inject('dbForProject') ->inject('audits') ->inject('usage') - ->action(function ($userId, $secret, $response, $user, $dbForProject, $audits, $usage) { + ->inject('events') + ->action(function ($userId, $secret, $response, $user, $dbForProject, $audits, $usage, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Document $user */ /** @var Utopia\Database\Database $dbForProject */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Stats\Stats $usage */ + /** @var Appwrite\Event\Event $events */ $profile = $dbForProject->getDocument('users', $userId); @@ -2200,5 +2278,11 @@ App::put('/v1/account/verification') $usage ->setParam('users.update', 1) ; + + $events + ->setParam('userId', $user->getId()) + ->setParam('tokenId', $verification->getId()) + ; + $response->dynamic($verification, Response::MODEL_TOKEN); }); diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 2bee190628..fb5b930b01 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -2,6 +2,7 @@ use Appwrite\Auth\Auth; use Appwrite\Auth\Validator\Password; +use Appwrite\Event\Validator\Event; use Appwrite\Network\Validator\CNAME; use Appwrite\Network\Validator\Domain as DomainValidator; use Appwrite\Network\Validator\Origin; @@ -585,7 +586,7 @@ App::post('/v1/projects/:projectId/webhooks') ->label('sdk.response.model', Response::MODEL_WEBHOOK) ->param('projectId', null, new UID(), 'Project unique ID.') ->param('name', null, new Text(128), 'Webhook name. Max length: 128 chars.') - ->param('events', null, new ArrayList(new WhiteList(array_keys(Config::getParam('events'), true), true)), 'Events list.') + ->param('events', null, new ArrayList(new Event()), 'Events list.') ->param('url', null, new URL(['http', 'https']), 'Webhook URL.') ->param('security', false, new Boolean(true), 'Certificate verification, false for disabled or true for enabled.') ->param('httpUser', '', new Text(256), 'Webhook HTTP user. Max length: 256 chars.', true) @@ -707,7 +708,7 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId') ->param('projectId', null, new UID(), 'Project unique ID.') ->param('webhookId', null, new UID(), 'Webhook unique ID.') ->param('name', null, new Text(128), 'Webhook name. Max length: 128 chars.') - ->param('events', null, new ArrayList(new WhiteList(array_keys(Config::getParam('events'), true), true)), 'Events list.') + ->param('events', null, new ArrayList(new Event()), 'Events list.') ->param('url', null, new URL(['http', 'https']), 'Webhook URL.') ->param('security', false, new Boolean(true), 'Certificate verification, false for disabled or true for enabled.') ->param('httpUser', '', new Text(256), 'Webhook HTTP user. Max length: 256 chars.', true) diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 6bb14c5003..3237e8d6ad 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -25,7 +25,7 @@ use Utopia\Validator\Boolean; App::post('/v1/users') ->desc('Create User') ->groups(['api', 'users']) - ->label('event', 'users.create') + ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'users') @@ -41,10 +41,12 @@ App::post('/v1/users') ->inject('response') ->inject('dbForProject') ->inject('usage') - ->action(function ($userId, $email, $password, $name, $response, $dbForProject, $usage) { + ->inject('events') + ->action(function ($userId, $email, $password, $name, $response, $dbForProject, $usage, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ /** @var Appwrite\Stats\Stats $usage */ + /** @var Appwrite\Event\Event $events */ $email = \strtolower($email); @@ -77,6 +79,10 @@ App::post('/v1/users') ->setParam('users.create', 1) ; + $events + ->setParam('userId', $user->getId()) + ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); $response->dynamic($user, Response::MODEL_USER); }); @@ -356,7 +362,7 @@ App::get('/v1/users/:userId/logs') App::patch('/v1/users/:userId/status') ->desc('Update User Status') ->groups(['api', 'users']) - ->label('event', 'users.update.status') + ->label('event', 'users.[userId].update.status') ->label('scope', 'users.write') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'users') @@ -370,10 +376,12 @@ App::patch('/v1/users/:userId/status') ->inject('response') ->inject('dbForProject') ->inject('usage') - ->action(function ($userId, $status, $response, $dbForProject, $usage) { + ->inject('events') + ->action(function ($userId, $status, $response, $dbForProject, $usage, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ /** @var Appwrite\Stats\Stats $usage */ + /** @var Appwrite\Event\Event $events */ $user = $dbForProject->getDocument('users', $userId); @@ -386,13 +394,18 @@ App::patch('/v1/users/:userId/status') $usage ->setParam('users.update', 1) ; + + $events + ->setParam('userId', $user->getId()) + ; + $response->dynamic($user, Response::MODEL_USER); }); App::patch('/v1/users/:userId/verification') ->desc('Update Email Verification') ->groups(['api', 'users']) - ->label('event', 'users.update.verification') + ->label('event', 'users.[userId].update.verification') ->label('scope', 'users.write') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'users') @@ -406,10 +419,12 @@ App::patch('/v1/users/:userId/verification') ->inject('response') ->inject('dbForProject') ->inject('usage') - ->action(function ($userId, $emailVerification, $response, $dbForProject, $usage) { + ->inject('events') + ->action(function ($userId, $emailVerification, $response, $dbForProject, $usage, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ /** @var Appwrite\Stats\Stats $usage */ + /** @var Appwrite\Event\Event $events */ $user = $dbForProject->getDocument('users', $userId); @@ -422,13 +437,18 @@ App::patch('/v1/users/:userId/verification') $usage ->setParam('users.update', 1) ; + + $events + ->setParam('userId', $user->getId()) + ; + $response->dynamic($user, Response::MODEL_USER); }); App::patch('/v1/users/:userId/name') ->desc('Update Name') ->groups(['api', 'users']) - ->label('event', 'users.update.name') + ->label('event', 'users.[userId].update.name') ->label('scope', 'users.write') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'users') @@ -442,10 +462,12 @@ App::patch('/v1/users/:userId/name') ->inject('response') ->inject('dbForProject') ->inject('audits') - ->action(function ($userId, $name, $response, $dbForProject, $audits) { + ->inject('events') + ->action(function ($userId, $name, $response, $dbForProject, $audits, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Event $events */ $user = $dbForProject->getDocument('users', $userId); @@ -456,9 +478,13 @@ App::patch('/v1/users/:userId/name') $user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('name', $name)); $audits + ->setPayload(array_merge($audits->getPayload(), [ + 'resource' => 'user/'.$user->getId() + ])) + ; + + $events ->setParam('userId', $user->getId()) - ->setParam('event', 'users.update.name') - ->setParam('resource', 'user/'.$user->getId()) ; $response->dynamic($user, Response::MODEL_USER); @@ -467,7 +493,7 @@ App::patch('/v1/users/:userId/name') App::patch('/v1/users/:userId/password') ->desc('Update Password') ->groups(['api', 'users']) - ->label('event', 'users.update.password') + ->label('event', 'users.[userId].update.password') ->label('scope', 'users.write') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'users') @@ -481,10 +507,12 @@ App::patch('/v1/users/:userId/password') ->inject('response') ->inject('dbForProject') ->inject('audits') - ->action(function ($userId, $password, $response, $dbForProject, $audits) { + ->inject('events') + ->action(function ($userId, $password, $response, $dbForProject, $audits, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Event $events */ $user = $dbForProject->getDocument('users', $userId); @@ -499,9 +527,13 @@ App::patch('/v1/users/:userId/password') $user = $dbForProject->updateDocument('users', $user->getId(), $user); $audits + ->setPayload(array_merge($audits->getPayload(), [ + 'resource' => 'user/'.$user->getId() + ])) + ; + + $events ->setParam('userId', $user->getId()) - ->setParam('event', 'users.update.password') - ->setParam('resource', 'user/'.$user->getId()) ; $response->dynamic($user, Response::MODEL_USER); @@ -510,7 +542,7 @@ App::patch('/v1/users/:userId/password') App::patch('/v1/users/:userId/email') ->desc('Update Email') ->groups(['api', 'users']) - ->label('event', 'users.update.email') + ->label('event', 'users.[userId].update.email') ->label('scope', 'users.write') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'users') @@ -524,10 +556,12 @@ App::patch('/v1/users/:userId/email') ->inject('response') ->inject('dbForProject') ->inject('audits') - ->action(function ($userId, $email, $response, $dbForProject, $audits) { + ->inject('events') + ->action(function ($userId, $email, $response, $dbForProject, $audits, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Event $events */ $user = $dbForProject->getDocument('users', $userId); @@ -548,10 +582,15 @@ App::patch('/v1/users/:userId/email') throw new Exception('Email already exists', 409, Exception::USER_EMAIL_ALREADY_EXISTS); } + $audits + ->setPayload(array_merge($audits->getPayload(), [ + 'resource' => 'user/'.$user->getId() + ])) + ; + + $events ->setParam('userId', $user->getId()) - ->setParam('event', 'users.update.email') - ->setParam('resource', 'user/'.$user->getId()) ; $response->dynamic($user, Response::MODEL_USER); @@ -560,7 +599,7 @@ App::patch('/v1/users/:userId/email') App::patch('/v1/users/:userId/prefs') ->desc('Update User Preferences') ->groups(['api', 'users']) - ->label('event', 'users.update.prefs') + ->label('event', 'users.[userId].update.prefs') ->label('scope', 'users.write') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'users') @@ -574,10 +613,12 @@ App::patch('/v1/users/:userId/prefs') ->inject('response') ->inject('dbForProject') ->inject('usage') - ->action(function ($userId, $prefs, $response, $dbForProject, $usage) { + ->inject('events') + ->action(function ($userId, $prefs, $response, $dbForProject, $usage, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ /** @var Appwrite\Stats\Stats $usage */ + /** @var Appwrite\Event\Event $events */ $user = $dbForProject->getDocument('users', $userId); @@ -590,13 +631,18 @@ App::patch('/v1/users/:userId/prefs') $usage ->setParam('users.update', 1) ; + + $events + ->setParam('userId', $user->getId()) + ; + $response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES); }); App::delete('/v1/users/:userId/sessions/:sessionId') ->desc('Delete User Session') ->groups(['api', 'users']) - ->label('event', 'users.sessions.delete') + ->label('event', 'users.[userId].sessions.[sessionId].delete') ->label('scope', 'users.write') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'users') @@ -623,36 +669,36 @@ App::delete('/v1/users/:userId/sessions/:sessionId') } $sessions = $user->getAttribute('sessions', []); + $key = array_search($sessionId, array_column($sessions, '$id'), true); - foreach ($sessions as $key => $session) { /** @var Document $session */ - - if ($sessionId == $session->getId()) { - unset($sessions[$key]); - - $dbForProject->deleteDocument('sessions', $session->getId()); - - $user->setAttribute('sessions', $sessions); - - $events - ->setParam('eventData', $response->output($user, Response::MODEL_USER)) - ; - - $dbForProject->updateDocument('users', $user->getId(), $user); - } + if (!$key) { + throw new Exception('Session not found', 404, Exception::USER_SESSION_NOT_FOUND); } + $dbForProject->deleteDocument('sessions', $sessions[$key]->getId()); + $events + ->setParam('eventData', $response->output($user, Response::MODEL_USER)) + ; + unset($sessions[$key]); + $user->setAttribute('sessions', $sessions); + $dbForProject->updateDocument('users', $user->getId(), $user); + $usage ->setParam('users.update', 1) ->setParam('users.sessions.delete', 1) ; + $events + ->setParam('userId', $user->getId()) + ; + $response->noContent(); }); App::delete('/v1/users/:userId/sessions') ->desc('Delete User Sessions') ->groups(['api', 'users']) - ->label('event', 'users.sessions.delete') + ->label('event', 'users.[userId].sessions.[sessionId].delete') ->label('scope', 'users.write') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'users') @@ -687,19 +733,21 @@ App::delete('/v1/users/:userId/sessions') $events ->setParam('eventData', $response->output($user, Response::MODEL_USER)) + ->setParam('userId', $user->getId()) ; $usage ->setParam('users.update', 1) ->setParam('users.sessions.delete', 1) ; + $response->noContent(); }); App::delete('/v1/users/:userId') ->desc('Delete User') ->groups(['api', 'users']) - ->label('event', 'users.delete') + ->label('event', 'users.[userId].delete') ->label('scope', 'users.write') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'users') @@ -719,7 +767,7 @@ App::delete('/v1/users/:userId') /** @var Appwrite\Event\Event $events */ /** @var Appwrite\Event\Event $deletes */ /** @var Appwrite\Stats\Stats $usage */ - + $user = $dbForProject->getDocument('users', $userId); if ($user->isEmpty() || $user->getAttribute('deleted')) { @@ -730,7 +778,7 @@ App::delete('/v1/users/:userId') * DO NOT DELETE THE USER RECORD ITSELF. * WE RETAIN THE USER RECORD TO RESERVE THE USER ID AND ENSURE THAT THE USER ID IS NOT REUSED. */ - + // clone user object to send to workers $clone = clone $user; @@ -752,6 +800,7 @@ App::delete('/v1/users/:userId') $events ->setParam('eventData', $response->output($clone, Response::MODEL_USER)) + ->setParam('userId', $user->getId()) ; $usage diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index c5c6da6167..f23d030a61 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -1,6 +1,7 @@ setEvent($route->getLabel('event', '')) + ->setProject($project) + ->setUser($user) + ; + $events ->setParam('projectId', $project->getId()) ->setParam('webhooks', $project->getAttribute('webhooks', [])) ->setParam('userId', $user->getId()) ->setParam('event', $route->getLabel('event', '')) ->setParam('eventData', []) - ->setParam('functionId', null) - ->setParam('executionId', null) + ->setParam('functionId', null) + ->setParam('executionId', null) ->setParam('trigger', 'event') ; $audits - ->setParam('projectId', $project->getId()) - ->setParam('userId', $user->getId()) - ->setParam('userEmail', $user->getAttribute('email')) - ->setParam('userName', $user->getAttribute('name')) - ->setParam('mode', $mode) - ->setParam('event', '') - ->setParam('resource', '') - ->setParam('userAgent', $request->getUserAgent('')) - ->setParam('ip', $request->getIP()) - ->setParam('data', []) + ->setEvent($route->getLabel('event', '')) + ->setProject($project) + ->setUser($user) + ->setPayload([ + 'mode' => $mode, + 'userAgent' => $request->getUserAgent(''), + 'ip' => $request->getIP(), + 'data' => [], + 'resource' => '' + ]) ; $usage @@ -196,19 +203,30 @@ App::shutdown(function ($utopia, $request, $response, $project, $events, $audits /** @var Appwrite\Event\Event $database */ /** @var bool $mode */ + if (!empty($events->getEvent())) { + $allEvents = Event::generateEvents($events->getEvent(), $events->getParams()); + var_dump($request->getRoute()->getPath(), $events->getEvent(), $allEvents); + foreach ($project->getAttribute('webhooks', []) as $webhook) { + /** + * @var Document $webhook + */ + if (array_intersect($webhook->getAttribute('events', []), $allEvents)) { + $events + ->setClass(Event::WEBHOOK_CLASS_NAME) + ->setQueue(Event::WEBHOOK_QUEUE_NAME) + ->setTrigger($webhook) + ->setPayload($response->getPayload()) + ->trigger(); + } + } + } if (!empty($events->getParam('event'))) { if (empty($events->getParam('eventData'))) { $events->setParam('eventData', $response->getPayload()); } - $webhooks = clone $events; $functions = clone $events; - $webhooks - ->setQueue('v1-webhooks') - ->setClass('WebhooksV1') - ->trigger(); - $functions ->setQueue('v1-functions') ->setClass('FunctionsV1') @@ -241,7 +259,7 @@ App::shutdown(function ($utopia, $request, $response, $project, $events, $audits } } - if (!empty($audits->getParam('event'))) { + if (!empty($audits->getEvent())) { $audits->trigger(); } diff --git a/app/realtime.php b/app/realtime.php index 8d36069edb..49ed5bad3e 100644 --- a/app/realtime.php +++ b/app/realtime.php @@ -362,10 +362,9 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, Console::error('Pub/sub error: ' . $th->getMessage()); $register->get('redisPool')->put($redis); $attempts++; + sleep(DATABASE_RECONNECT_SLEEP); continue; } - - $attempts++; } Console::error('Failed to restart pub/sub...'); diff --git a/app/workers/audits.php b/app/workers/audits.php index d83c832c37..5819a31384 100644 --- a/app/workers/audits.php +++ b/app/workers/audits.php @@ -1,8 +1,10 @@ args['projectId']; - $userId = $this->args['userId']; - $userName = $this->args['userName']; - $userEmail = $this->args['userEmail']; - $mode = $this->args['mode']; - $event = $this->args['event']; - $resource = $this->args['resource']; - $userAgent = $this->args['userAgent']; - $ip = $this->args['ip']; - $data = $this->args['data']; - - $dbForProject = $this->getProjectDB($projectId); - $audit = new Audit($dbForProject); + $events = $this->args['events']; + $user = new Document($this->args['user']); + $project = new Document($this->args['project']); + $payload = $this->args['payload']; - $audit->log($userId, $event, $resource, $userAgent, $ip, '', [ + $userName = $user->getAttribute('name', ''); + $userEmail = $user->getAttribute('email', ''); + + $event = $events[0]; + $mode = $payload['mode']; + $resource = $payload['resource']; + $userAgent = $payload['userAgent']; + $ip = $payload['ip']; + $data = $payload['data']; + + $dbForProject = $this->getProjectDB($project->getId()); + $audit = new Audit($dbForProject); + $audit->log($user->getId(), $event, $resource, $userAgent, $ip, '', [ 'userName' => $userName, 'userEmail' => $userEmail, 'mode' => $mode, diff --git a/app/workers/mails.php b/app/workers/mails.php index 25ddb438c0..dc924a8867 100644 --- a/app/workers/mails.php +++ b/app/workers/mails.php @@ -4,6 +4,7 @@ use Appwrite\Resque\Worker; use Appwrite\Template\Template; use Utopia\App; use Utopia\CLI\Console; +use Utopia\Database\Document; use Utopia\Locale\Locale; require_once __DIR__ . '/../init.php'; @@ -30,26 +31,28 @@ class MailsV1 extends Worker return; } - $recipient = $this->args['recipient']; - $name = $this->args['name']; - $url = $this->args['url']; - $project = $this->args['project']; - $type = $this->args['type']; + $payload = $this->args['payload']; + + $recipient = $payload['recipient']; + $url = $payload['url']; + $project = $payload['project']; + $type = $payload['type']; + $name = $payload['name'] ?? $recipient; $prefix = $this->getPrefix($type); - $locale = new Locale($this->args['locale']); + $locale = new Locale($payload['locale']); if (!$this->doesLocaleExist($locale, $prefix)) { $locale->setDefault('en'); } - $from = $this->args['from'] === 'console' ? '' : \sprintf($locale->getText('emails.sender'), $project); + $from = $payload['from'] === 'console' ? '' : \sprintf($locale->getText('emails.sender'), $project); $body = Template::fromFile(__DIR__ . '/../config/locale/templates/email-base.tpl'); $subject = ''; switch ($type) { case MAIL_TYPE_INVITATION: - $subject = \sprintf($locale->getText("$prefix.subject"), $this->args['team'], $project); - $body->setParam('{{owner}}', $this->args['owner']); - $body->setParam('{{team}}', $this->args['team']); + $subject = \sprintf($locale->getText("$prefix.subject"), $payload['team'], $project); + $body->setParam('{{owner}}', $payload['owner']); + $body->setParam('{{team}}', $payload['team']); break; case MAIL_TYPE_RECOVERY: case MAIL_TYPE_VERIFICATION: diff --git a/app/workers/webhooks.php b/app/workers/webhooks.php index 8613472216..571bbe7102 100644 --- a/app/workers/webhooks.php +++ b/app/workers/webhooks.php @@ -3,15 +3,17 @@ use Appwrite\Resque\Worker; use Utopia\App; use Utopia\CLI\Console; +use Utopia\Database\Document; -require_once __DIR__.'/../init.php'; +require_once __DIR__ . '/../init.php'; Console::title('Webhooks V1 Worker'); Console::success(APP_NAME . ' webhooks worker v1 has started'); class WebhooksV1 extends Worker { - public function getName(): string { + public function getName(): string + { return "webhooks"; } @@ -24,68 +26,57 @@ class WebhooksV1 extends Worker $errors = []; // Event - $projectId = $this->args['projectId'] ?? ''; - $webhooks = $this->args['webhooks'] ?? []; - $userId = $this->args['userId'] ?? ''; - $event = $this->args['event'] ?? ''; - $eventData = \json_encode($this->args['eventData']); + $events = $this->args['events']; + $user = new Document($this->args['user']); + $project = new Document($this->args['project']); + $webhook = new Document($this->args['trigger'] ?? []); + $payload = \json_encode($this->args['payload']); - foreach ($webhooks as $webhook) { - if (!(isset($webhook['events']) && \is_array($webhook['events']) && \in_array($event, $webhook['events']))) { - continue; - } + $httpUser = $webhook->getAttribute('httpUser'); + $httpPass = $webhook->getAttribute('httpPass'); - $id = $webhook['$id'] ?? ''; - $name = $webhook['name'] ?? ''; - $signature = $webhook['signature'] ?? 'not-yet-implemented'; - $url = $webhook['url'] ?? ''; - $security = (bool) ($webhook['security'] ?? true); - $httpUser = $webhook['httpUser'] ?? null; - $httpPass = $webhook['httpPass'] ?? null; + $ch = \curl_init($webhook->getAttribute('url')); - $ch = \curl_init($url); + \curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); + \curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); + \curl_setopt($ch, CURLOPT_HEADER, 0); + \curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + \curl_setopt($ch, CURLOPT_USERAGENT, \sprintf( + APP_USERAGENT, + App::getEnv('_APP_VERSION', 'UNKNOWN'), + App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY) + )); + \curl_setopt( + $ch, + CURLOPT_HTTPHEADER, + [ + 'Content-Type: application/json', + 'Content-Length: ' . \strlen($payload), + 'X-' . APP_NAME . '-Webhook-Id: ' . $webhook->getId(), + 'X-' . APP_NAME . '-Webhook-Event: ' . implode(', ', $events), + 'X-' . APP_NAME . '-Webhook-Name: ' . $webhook->getAttribute('name', ''), + 'X-' . APP_NAME . '-Webhook-User-Id: ' . $user->getId(), + 'X-' . APP_NAME . '-Webhook-Project-Id: ' . $project->getId(), + 'X-' . APP_NAME . '-Webhook-Signature: ' . $webhook->getAttribute('signature') ?? 'not-yet-implemented', + ] + ); - \curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); - \curl_setopt($ch, CURLOPT_POSTFIELDS, $eventData); - \curl_setopt($ch, CURLOPT_HEADER, 0); - \curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - \curl_setopt($ch, CURLOPT_USERAGENT, \sprintf( - APP_USERAGENT, - App::getEnv('_APP_VERSION', 'UNKNOWN'), - App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY) - )); - \curl_setopt( - $ch, - CURLOPT_HTTPHEADER, - [ - 'Content-Type: application/json', - 'Content-Length: ' . \strlen($eventData), - 'X-' . APP_NAME . '-Webhook-Id: ' . $id, - 'X-' . APP_NAME . '-Webhook-Event: ' . $event, - 'X-' . APP_NAME . '-Webhook-Name: ' . $name, - 'X-' . APP_NAME . '-Webhook-User-Id: ' . $userId, - 'X-' . APP_NAME . '-Webhook-Project-Id: ' . $projectId, - 'X-' . APP_NAME . '-Webhook-Signature: ' . $signature, - ] - ); - - if (!$security) { - \curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); - \curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); - } - - if (!empty($httpUser) && !empty($httpPass)) { - \curl_setopt($ch, CURLOPT_USERPWD, "$httpUser:$httpPass"); - \curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); - } - - if (false === \curl_exec($ch)) { - $errors[] = \curl_error($ch) . ' in event ' . $event . ' for webhook ' . $name; - } - - \curl_close($ch); + if (!$webhook->getAttribute('security', true)) { + \curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); + \curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); } + if (!empty($httpUser) && !empty($httpPass)) { + \curl_setopt($ch, CURLOPT_USERPWD, "$httpUser:$httpPass"); + \curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); + } + + if (false === \curl_exec($ch)) { + $errors[] = \curl_error($ch) . ' in events ' . implode(', ', $events) . ' for webhook ' . $webhook->getAttribute('name'); + } + + \curl_close($ch); + if (!empty($errors)) { throw new Exception(\implode(" / \n\n", $errors)); } diff --git a/src/Appwrite/Event/Event.php b/src/Appwrite/Event/Event.php index 3985a807f4..22a71b946a 100644 --- a/src/Appwrite/Event/Event.php +++ b/src/Appwrite/Event/Event.php @@ -5,6 +5,7 @@ namespace Appwrite\Event; use Exception; use InvalidArgumentException; use Resque; +use Utopia\Database\Document; class Event { @@ -37,7 +38,12 @@ class Event protected string $queue = ''; protected string $class = ''; + protected string $event = ''; protected array $params = []; + protected array $payload = []; + protected ?Document $project = null; + protected ?Document $user = null; + protected ?Document $trigger = null; /** * @param string $queue @@ -59,6 +65,7 @@ class Event public function setQueue(string $queue): self { $this->queue = $queue; + return $this; } @@ -72,6 +79,76 @@ class Event return $this->queue; } + /** + * Set event name used for this event. + * @param string $event + * @return Event + */ + public function setEvent(string $event): self + { + $this->event = $event; + + return $this; + } + + /** + * Get event name used for this event. + * + * @return string + */ + public function getEvent(): string + { + return $this->event; + } + + public function setProject(Document $project): self + { + $this->projectId = $project; + + return $this; + } + + public function getProjectId(): Document + { + return $this->projectId; + } + + public function setUser(Document $user): self + { + $this->userId = $user; + + return $this; + } + + public function getUserId(): Document + { + return $this->userId; + } + + public function setPayload(array $payload): self + { + $this->payload = $payload; + + return $this; + } + + public function getPayload(): array + { + return $this->payload; + } + + public function setTrigger(Document $trigger): self + { + $this->trigger = $trigger; + + return $this; + } + + public function getTrigger(): Document + { + return $this->trigger; + } + /** * Set class used for this event. * @param string $class @@ -80,6 +157,7 @@ class Event public function setClass(string $class): self { $this->class = $class; + return $this; } @@ -118,17 +196,31 @@ class Event return $this->params[$key] ?? null; } + /** + * Get all params of the event. + * + * @return array + */ + public function getParams(): array + { + return $this->params; + } + /** * Execute Event. * - * @return Event + * @return string|bool * @throws InvalidArgumentException */ - public function trigger(): self + public function trigger(): string|bool { - Resque::enqueue($this->queue, $this->class, $this->params); - - return $this->reset(); + return Resque::enqueue($this->queue, $this->class, [ + 'project' => $this->projectId, + 'user' => $this->userId, + 'payload' => $this->payload, + 'trigger' => $this->trigger, + 'events' => Event::generateEvents($this->getEvent(), $this->getParams()) + ]); } /** @@ -143,50 +235,64 @@ class Event return $this; } - static function generateEvents(string $pattern, array $params = []): array + public static function parseEventPattern(string $pattern): array { $parts = \explode('.', $pattern); $count = \count($parts); - if ($count < 2 || $count > 6) { - throw new Exception("Patten incorrect."); - } - /** * Identify all sestions of the pattern. */ - $type = $parts[0]; - $action = match ($count) { - 2 => $parts[1], - 3, 4 => $parts[2], - 5, 6 => $parts[4] - }; + $type = $parts[0] ?? false; + $resource = $parts[1] ?? false; + $hasSubResource = $count > 3 && \str_starts_with($parts[3], '['); - if ($count > 4) { + if ($hasSubResource) { $subType = $parts[2]; $subResource = $parts[3]; if ($count === 6) { $attribute = $parts[5]; } - } - if ($count > 2) { - $resource = $parts[1]; + } else { if ($count === 4) { $attribute = $parts[3]; } } + $subType ??= false; + $subResource ??= false; + $attribute ??= false; + $action = match (true) { + !$hasSubResource && $count > 2 => $parts[2], + $hasSubResource && $count > 4 => $parts[4], + default => false + }; + + return [ + 'type' => $type, + 'resource' => $resource, + 'subType' => $subType, + 'subResource' => $subResource, + 'action' => $action, + 'attribute' => $attribute, + ]; + } + + static function generateEvents(string $pattern, array $params = []): array + { + $params = \array_filter($params, fn($param) => !\is_array($param)); $paramKeys = \array_keys($params); $paramValues = \array_values($params); $patterns = []; - $resource ??= false; - $subResource ??= false; - $attribute ??= false; - if (empty($params) && ($type ?? false) && !$resource) { - return [$pattern]; - } + $parsed = self::parseEventPattern($pattern); + $type = $parsed['type']; + $resource = $parsed['resource']; + $subType = $parsed['subType']; + $subResource = $parsed['subResource']; + $action = $parsed['action']; + $attribute = $parsed['attribute']; if ($resource && !\in_array(\trim($resource, '[]'), $paramKeys)) { throw new InvalidArgumentException("{$resource} is missing from the params."); @@ -244,6 +350,7 @@ class Event * Remove [] from the events. */ $events = \array_map(fn (string $event) => \str_replace(['[', ']'], '', $event), $events); + $events = \array_unique($events); return $events; } diff --git a/src/Appwrite/Event/Validator/Event.php b/src/Appwrite/Event/Validator/Event.php new file mode 100644 index 0000000000..66086bc946 --- /dev/null +++ b/src/Appwrite/Event/Validator/Event.php @@ -0,0 +1,172 @@ + [ + 'subTypes' => [ + 'sessions', + 'recovery', + 'verification' + ], + 'attributes' => [ + 'email', + 'name', + 'password', + 'status', + 'prefs', + ] + ], + 'collections' => [ + 'subTypes' => [ + 'documents', + 'attributes', + 'indexes' + ] + ], + 'buckets' => [ + 'subTypes' => [ + 'files' + ] + ], + 'teams' => [ + 'subTypes' => [ + 'memberships' => [ + 'attributes' => [ + 'status' + ] + ] + ] + ], + 'functions' => [ + 'subTypes' => [ + 'deployments', + 'executions' + ] + ], + ]; + + protected array $actions = [ + 'create', + 'update', + 'delete' + ]; + /** + * Get Description. + * + * Returns validator description + * + * @return string + */ + public function getDescription(): string + { + return 'Password must be at least 8 characters'; + } + + /** + * Is valid. + * + * @param mixed $value + * + * @return bool + */ + public function isValid($value): bool + { + $parts = \explode('.', $value); + $count = \count($parts); + + if ($count < 2 || $count > 6) { + return false; + } + + /** + * Identify all sestions of the pattern. + */ + $type = $parts[0] ?? false; + $resource = $parts[1] ?? false; + $hasSubResource = $count > 3 && \array_key_exists('subTypes', $this->types[$type]) && \in_array($parts[2], $this->types[$type]['subTypes']); + + if (!$type || !$resource) { + return false; + } + + if ($hasSubResource) { + $subType = $parts[2]; + $subResource = $parts[3]; + if ($count === 6) { + $attribute = $parts[5]; + } + } else { + if ($count === 4) { + $attribute = $parts[3]; + } + } + + $subType ??= false; + $subResource ??= false; + $attribute ??= false; + $action = match (true) { + !$hasSubResource && $count > 2 => $parts[2], + $hasSubResource && $count > 4 => $parts[4], + default => false + }; + + if ($action && !\in_array($action, $this->actions)) { + return false; + } + + if (!\in_array($type, \array_keys($this->types))) { + return false; + } + + if ($subtype ?? false) { + if (!($subResource ?? false) || !\in_array($subType, $this->types[$type]['subTypes'])) { + return false; + } + } + + if ($attribute ?? false) { + if ( + (\array_key_exists('attributes', $this->types[$type]) && !\in_array($attribute, $this->types[$type]['attributes'])) || + (($subType ?? false) && \array_key_exists('attributes', $this->types[$type]['subTypes'][$subType]) && !\in_array($attribute, $this->types[$type]['subTypes'][$subType]['attributes'])) + ) { + return false; + } + } + + return true; + } + + /** + * Is array + * + * Function will return true if object is array. + * + * @return bool + */ + public function isArray(): bool + { + return false; + } + + /** + * Get Type + * + * Returns validator type. + * + * @return string + */ + public function getType(): string + { + return self::TYPE_STRING; + } +} diff --git a/tests/e2e/Scopes/ProjectCustom.php b/tests/e2e/Scopes/ProjectCustom.php index 07b5d3be5c..049583743c 100644 --- a/tests/e2e/Scopes/ProjectCustom.php +++ b/tests/e2e/Scopes/ProjectCustom.php @@ -98,54 +98,11 @@ trait ProjectCustom ], [ 'name' => 'Webhook Test', 'events' => [ - 'account.create', - 'account.update.email', - 'account.update.name', - 'account.update.password', - 'account.update.prefs', - 'account.recovery.create', - 'account.recovery.update', - 'account.verification.create', - 'account.verification.update', - 'account.delete', - 'account.sessions.create', - 'account.sessions.update', - 'account.sessions.delete', - 'database.collections.create', - 'database.collections.update', - 'database.collections.delete', - 'database.attributes.create', - 'database.attributes.delete', - 'database.indexes.create', - 'database.indexes.delete', - 'database.documents.create', - 'database.documents.update', - 'database.documents.delete', - 'functions.create', - 'functions.update', - 'functions.delete', - 'functions.deployments.create', - 'functions.deployments.update', - 'functions.deployments.delete', - 'functions.executions.create', - 'functions.executions.update', - 'storage.files.create', - 'storage.files.update', - 'storage.files.delete', - 'storage.buckets.create', - 'storage.buckets.update', - 'storage.buckets.delete', - 'users.create', - 'users.update.prefs', - 'users.update.status', - 'users.delete', - 'users.sessions.delete', - 'teams.create', - 'teams.update', - 'teams.delete', - 'teams.memberships.create', - 'teams.memberships.update.status', - 'teams.memberships.delete', + 'collections.*', + 'functions.*', + 'buckets.*', + 'teams.*', + 'users.*' ], 'url' => 'http://request-catcher:5000/webhook', 'security' => false, diff --git a/tests/e2e/Services/Account/AccountBase.php b/tests/e2e/Services/Account/AccountBase.php index 1bb489c0a1..8e7cfef3d1 100644 --- a/tests/e2e/Services/Account/AccountBase.php +++ b/tests/e2e/Services/Account/AccountBase.php @@ -310,7 +310,8 @@ trait AccountBase { sleep(10); $session = $data['session'] ?? ''; - + $sessionId = $data['sessionId'] ?? ''; + $userId = $data['id'] ?? ''; /** * Test for SUCCESS */ @@ -326,8 +327,7 @@ trait AccountBase $this->assertNotEmpty($response['body']['logs']); $this->assertCount(2, $response['body']['logs']); $this->assertIsNumeric($response['body']['total']); - - $this->assertContains($response['body']['logs'][0]['event'], ['account.create', 'account.sessions.create']); + $this->assertContains($response['body']['logs'][0]['event'], ["users.{$userId}.create", "users.{$userId}.sessions.{$sessionId}.create"]); $this->assertEquals($response['body']['logs'][0]['ip'], filter_var($response['body']['logs'][0]['ip'], FILTER_VALIDATE_IP)); $this->assertIsNumeric($response['body']['logs'][0]['time']); @@ -349,7 +349,7 @@ trait AccountBase $this->assertEquals('--', $response['body']['logs'][0]['countryCode']); $this->assertEquals('Unknown', $response['body']['logs'][0]['countryName']); - $this->assertContains($response['body']['logs'][1]['event'], ['account.create', 'account.sessions.create']); + $this->assertContains($response['body']['logs'][1]['event'], ["users.{$userId}.create", "users.{$userId}.sessions.{$sessionId}.create"]); $this->assertEquals($response['body']['logs'][1]['ip'], filter_var($response['body']['logs'][1]['ip'], FILTER_VALIDATE_IP)); $this->assertIsNumeric($response['body']['logs'][1]['time']); diff --git a/tests/unit/Event/EventTest.php b/tests/unit/Event/EventTest.php index 52afc58d5c..7900a73710 100644 --- a/tests/unit/Event/EventTest.php +++ b/tests/unit/Event/EventTest.php @@ -87,9 +87,14 @@ class EventTest extends TestCase public function testGenerateEvents() { - $event = Event::generateEvents('users.create'); - $this->assertCount(1, $event); - $this->assertContains('users.create', $event); + $event = Event::generateEvents('users.[userId].create', [ + 'userId' => 'torsten' + ]); + $this->assertCount(4, $event); + $this->assertContains('users.torsten.create', $event); + $this->assertContains('users.torsten', $event); + $this->assertContains('users.*.create', $event); + $this->assertContains('users.*', $event); $event = Event::generateEvents('users.[userId].update.email', [ 'userId' => 'torsten' diff --git a/tests/unit/Event/Validator/EventValidatorTest.php b/tests/unit/Event/Validator/EventValidatorTest.php new file mode 100644 index 0000000000..92898bd379 --- /dev/null +++ b/tests/unit/Event/Validator/EventValidatorTest.php @@ -0,0 +1,51 @@ +object = new Event(); + } + + public function tearDown(): void + { + } + + public function testValues() + { + $this->assertTrue($this->object->isValid('users.*.create')); + $this->assertTrue($this->object->isValid('users.torsten.update')); + $this->assertTrue($this->object->isValid('users.torsten')); + $this->assertTrue($this->object->isValid('users.*.update.email')); + $this->assertTrue($this->object->isValid('users.*.update')); + $this->assertTrue($this->object->isValid('users.*')); + $this->assertTrue($this->object->isValid('collections.chapters.documents.prolog.create')); + $this->assertTrue($this->object->isValid('collections.chapters.documents.prolog')); + $this->assertTrue($this->object->isValid('collections.chapters.documents.*.create')); + $this->assertTrue($this->object->isValid('collections.chapters.documents.*')); + $this->assertTrue($this->object->isValid('collections.*.documents.prolog.create')); + $this->assertTrue($this->object->isValid('collections.*.documents.prolog')); + $this->assertTrue($this->object->isValid('collections.*.documents.*.create')); + $this->assertTrue($this->object->isValid('collections.*.documents.*')); + $this->assertTrue($this->object->isValid('collections.*')); + $this->assertTrue($this->object->isValid('functions.*')); + $this->assertTrue($this->object->isValid('buckets.*')); + $this->assertTrue($this->object->isValid('teams.*')); + $this->assertTrue($this->object->isValid('users.*')); + + $this->assertFalse($this->object->isValid(false)); + $this->assertFalse($this->object->isValid(null)); + $this->assertFalse($this->object->isValid('')); + $this->assertFalse($this->object->isValid('collections')); + $this->assertFalse($this->object->isValid('collections.*.unknown')); + $this->assertFalse($this->object->isValid('collections.*.documents.*.unknown')); + $this->assertFalse($this->object->isValid('users.torsten.unknown')); + } +} From 87b8fc3c41200fb31c865d570e5d49683c43e2a9 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Wed, 13 Apr 2022 14:39:31 +0200 Subject: [PATCH 03/35] feat: event classes for different cases --- app/controllers/api/account.php | 282 +++++----------- app/controllers/api/database.php | 311 ++++++++++-------- app/controllers/api/functions.php | 190 +++++++---- app/controllers/api/projects.php | 23 +- app/controllers/api/storage.php | 127 +++---- app/controllers/api/teams.php | 111 ++++--- app/controllers/api/users.php | 26 +- app/controllers/general.php | 11 +- app/controllers/shared/api.php | 128 ++++--- app/init.php | 12 +- app/tasks/maintenance.php | 48 +-- app/tasks/ssl.php | 18 +- app/workers/audits.php | 15 +- app/workers/builds.php | 8 +- app/workers/certificates.php | 17 +- app/workers/database.php | 19 +- app/workers/deletes.php | 46 +-- app/workers/functions.php | 166 +++++----- app/workers/mails.php | 26 +- app/workers/webhooks.php | 30 +- src/Appwrite/Event/Audit.php | 81 +++++ src/Appwrite/Event/Certificate.php | 64 ++++ src/Appwrite/Event/Database.php | 66 ++++ src/Appwrite/Event/Delete.php | 50 +++ src/Appwrite/Event/Event.php | 22 +- src/Appwrite/Event/Mail.php | 110 +++++++ src/Appwrite/Messaging/Adapter/Realtime.php | 119 ++++--- .../Functions/FunctionsConsoleClientTest.php | 4 +- .../Functions/FunctionsCustomClientTest.php | 8 +- .../Functions/FunctionsCustomServerTest.php | 262 +++++++-------- .../Realtime/RealtimeCustomClientTest.php | 58 ++-- 31 files changed, 1420 insertions(+), 1038 deletions(-) create mode 100644 src/Appwrite/Event/Audit.php create mode 100644 src/Appwrite/Event/Certificate.php create mode 100644 src/Appwrite/Event/Database.php create mode 100644 src/Appwrite/Event/Delete.php create mode 100644 src/Appwrite/Event/Mail.php diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 2c805618fc..1907c9bb8e 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -60,7 +60,7 @@ App::post('/v1/account') /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Document $project */ /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Stats\Stats $usage */ /** @var Appwrite\Event\Event $events */ @@ -120,11 +120,8 @@ App::post('/v1/account') Authorization::setRole('role:' . Auth::USER_ROLE_MEMBER); $audits - ->setParam('userId', $user->getId()) + ->setResource('user/'.$user->getId()) ->setUser($user) - ->setPayload(array_merge($audits->getPayload(), [ - 'resource' => 'user/'.$user->getId() - ])) ; $usage @@ -169,7 +166,7 @@ App::post('/v1/account/sessions') /** @var Utopia\Database\Database $dbForProject */ /** @var Utopia\Locale\Locale $locale */ /** @var MaxMind\Db\Reader $geodb */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Stats\Stats $usage */ /** @var Appwrite\Event\Event $events */ @@ -215,12 +212,8 @@ App::post('/v1/account/sessions') $profile = $dbForProject->updateDocument('users', $profile->getId(), $profile); $audits - ->setParam('userId', $profile->getId()) - ->setParam('sessionId', $session->getId()) + ->setResource('user/'.$profile->getId()) ->setUser($profile) - ->setPayload(array_merge($audits->getPayload(), [ - 'resource' => 'user/'.$profile->getId() - ])) ; if (!Config::getParam('domainVerification')) { @@ -399,7 +392,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') /** @var Utopia\Database\Document $user */ /** @var Utopia\Database\Database $dbForProject */ /** @var MaxMind\Db\Reader $geodb */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Event\Event $events */ /** @var Appwrite\Stats\Stats $usage */ @@ -577,12 +570,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $user = $dbForProject->updateDocument('users', $user->getId(), $user); $audits - ->setParam('userId', $user->getId()) - ->setParam('sessionId', $session->getId()) + ->setResource('user/'.$user->getId()) ->setUser($user) - ->setPayload(array_merge($audits->getPayload(), [ - 'resource' => 'user/'.$user->getId() - ])) ; $usage @@ -656,9 +645,9 @@ App::post('/v1/account/sessions/magic-url') /** @var Utopia\Database\Document $project */ /** @var Utopia\Database\Database $dbForProject */ /** @var Utopia\Locale\Locale $locale */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Event\Event $events */ - /** @var Appwrite\Event\Event $mails */ + /** @var Appwrite\Event\Mail $mails */ if(empty(App::getEnv('_APP_SMTP_HOST'))) { throw new Exception('SMTP Disabled', 503, Exception::GENERAL_SMTP_DISABLED); @@ -703,8 +692,6 @@ App::post('/v1/account/sessions/magic-url') 'search' => implode(' ', [$userId, $email]), 'deleted' => false ]))); - - $mails->setParam('event', 'users.create'); } $loginSecret = Auth::tokenGenerator(); @@ -740,14 +727,10 @@ App::post('/v1/account/sessions/magic-url') $url = Template::unParseURL($url); $mails - ->setPayload([ - 'from' => $project->getId(), - 'recipient' => $user->getAttribute('email'), - 'url' => $url, - 'locale' => $locale->default, - 'project' => $project->getAttribute('name', ['[APP-NAME]']), - 'type' => MAIL_TYPE_MAGIC_SESSION - ]) + ->setType(MAIL_TYPE_MAGIC_SESSION) + ->setRecipient($user->getAttribute('email')) + ->setUrl($url) + ->setLocale($locale->default) ->trigger() ; @@ -763,10 +746,8 @@ App::post('/v1/account/sessions/magic-url') ($isPrivilegedUser || $isAppUser) ? $loginSecret : ''); $audits + ->setResource('user/'.$user->getId()) ->setUser($user) - ->setPayload(array_merge($audits->getPayload(), [ - 'resource' => 'user/'.$user->getId() - ])) ; $response @@ -806,7 +787,7 @@ App::put('/v1/account/sessions/magic-url') /** @var Utopia\Database\Database $dbForProject */ /** @var Utopia\Locale\Locale $locale */ /** @var MaxMind\Db\Reader $geodb */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Event\Event $events */ $user = $dbForProject->getDocument('users', $userId); @@ -871,13 +852,7 @@ App::put('/v1/account/sessions/magic-url') throw new Exception('Failed saving user to DB', 500, Exception::GENERAL_SERVER_ERROR); } - $audits - ->setParam('userId', $user->getId()) - ->setParam('sessionId', $session->getId()) - ->setPayload(array_merge($audits->getPayload(), [ - 'resource' => 'user/'.$user->getId() - ])) - ; + $audits->setResource('user/'.$user->getId()); $events ->setParam('userId', $user->getId()) @@ -943,7 +918,7 @@ App::post('/v1/account/sessions/anonymous') /** @var Utopia\Database\Document $project */ /** @var Utopia\Database\Database $dbForProject */ /** @var MaxMind\Db\Reader $geodb */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Stats\Stats $usage */ /** @var Appwrite\Stats\Stats $events */ @@ -1022,13 +997,7 @@ App::post('/v1/account/sessions/anonymous') $user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('sessions', $session, Document::SET_TYPE_APPEND)); - $audits - ->setParam('userId', $user->getId()) - ->setParam('sessionId', $session->getId()) - ->setPayload(array_merge($audits->getPayload(), [ - 'resource' => 'user/'.$user->getId() - ])) - ; + $audits->setResource('user/'.$user->getId()); $usage ->setParam('users.sessions.create', 1) @@ -1350,7 +1319,7 @@ App::patch('/v1/account/name') /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Document $user */ /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Stats\Stats $usage */ /** @var Appwrite\Stats\Stats $events */ @@ -1360,20 +1329,12 @@ App::patch('/v1/account/name') ); $audits - ->setParam('userId', $user->getId()) + ->setResource('user/'.$user->getId()) ->setUser($user) - ->setPayload(array_merge($audits->getPayload(), [ - 'resource' => 'user/'.$user->getId() - ])) ; - $usage - ->setParam('users.update', 1) - ; - - $events - ->setParam('userId', $user->getId()) - ; + $usage->setParam('users.update', 1); + $events->setParam('userId', $user->getId()); $response->dynamic($user, Response::MODEL_USER); }); @@ -1402,7 +1363,7 @@ App::patch('/v1/account/password') /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Document $user */ /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Stats\Stats $usage */ /** @var Appwrite\Stats\Stats $events */ @@ -1417,20 +1378,12 @@ App::patch('/v1/account/password') ); $audits - ->setParam('userId', $user->getId()) + ->setResource('user/'.$user->getId()) ->setUser($user) - ->setPayload(array_merge($audits->getPayload(), [ - 'resource' => 'user/'.$user->getId() - ])) ; - $usage - ->setParam('users.update', 1) - ; - - $events - ->setParam('userId', $user->getId()) - ; + $usage->setParam('users.update', 1); + $events->setParam('userId', $user->getId()); $response->dynamic($user, Response::MODEL_USER); }); @@ -1459,7 +1412,7 @@ App::patch('/v1/account/email') /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Document $user */ /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Stats\Stats $usage */ /** @var Appwrite\Stats\Stats $events */ @@ -1491,20 +1444,12 @@ App::patch('/v1/account/email') } $audits - ->setParam('userId', $user->getId()) + ->setResource('user/'.$user->getId()) ->setUser($user) - ->setPayload(array_merge($audits->getPayload(), [ - 'resource' => 'user/'.$user->getId() - ])) ; - $usage - ->setParam('users.update', 1) - ; - - $events - ->setParam('userId', $user->getId()) - ; + $usage->setParam('users.update', 1); + $events->setParam('userId', $user->getId()); $response->dynamic($user, Response::MODEL_USER); }); @@ -1532,26 +1477,15 @@ App::patch('/v1/account/prefs') /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Document $user */ /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Stats\Stats $usage */ /** @var Appwrite\Event\Event $events */ $user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('prefs', $prefs)); - $audits - ->setParam('userId', $user->getId()) - ->setPayload(array_merge($audits->getPayload(), [ - 'resource' => 'user/'.$user->getId() - ])) - ; - - $usage - ->setParam('users.update', 1) - ; - - $events - ->setParam('userId', $user->getId()) - ; + $audits->setResource('user/'.$user->getId()); + $usage->setParam('users.update', 1); + $events->setParam('userId', $user->getId()); $response->dynamic($user, Response::MODEL_USER); }); @@ -1579,7 +1513,7 @@ App::delete('/v1/account') /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Document $user */ /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Event\Event $events */ /** @var Appwrite\Stats\Stats $usage */ @@ -1597,10 +1531,8 @@ App::delete('/v1/account') */ $audits - ->setParam('userId', $user->getId()) - ->setParam('event', 'account.delete') - ->setParam('resource', 'user/' . $user->getId()) - ->setParam('data', $user->getArrayCopy()) + ->setResource('user/' . $user->getId()) + ->setPayload($response->output($user, Response::MODEL_USER)) ; $events @@ -1609,14 +1541,11 @@ App::delete('/v1/account') ; if (!Config::getParam('domainVerification')) { - $response - ->addHeader('X-Fallback-Cookies', \json_encode([])) - ; + $response->addHeader('X-Fallback-Cookies', \json_encode([])); } - $usage - ->setParam('users.delete', 1) - ; + $usage->setParam('users.delete', 1); + $response ->addCookie(Auth::$cookieName . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) @@ -1651,7 +1580,7 @@ App::delete('/v1/account/sessions/:sessionId') /** @var Utopia\Database\Document $user */ /** @var Utopia\Database\Database $dbForProject */ /** @var Utopia\Locale\Locale $locale */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Event\Event $events */ /** @var Appwrite\Stats\Stats $usage */ @@ -1668,11 +1597,7 @@ App::delete('/v1/account/sessions/:sessionId') $dbForProject->deleteDocument('sessions', $session->getId()); - $audits - ->setParam('userId', $user->getId()) - ->setParam('event', 'account.sessions.delete') - ->setParam('resource', 'user/' . $user->getId()) - ; + $audits->setResource('user/' . $user->getId()); $session->setAttribute('current', false); @@ -1697,9 +1622,9 @@ App::delete('/v1/account/sessions/:sessionId') $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('sessions', $sessions)); $events - ->setPayload($response->output($session, Response::MODEL_SESSION)) ->setParam('userId', $user->getId()) ->setParam('sessionId', $session->getId()) + ->setPayload($response->output($session, Response::MODEL_SESSION)) ; $usage @@ -1744,7 +1669,7 @@ App::patch('/v1/account/sessions/:sessionId') /** @var Utopia\Database\Database $dbForProject */ /** @var Utopia\Database\Document $project */ /** @var Utopia\Locale\Locale $locale */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Event\Event $events */ /** @var Appwrite\Stats\Stats $usage */ @@ -1773,7 +1698,7 @@ App::patch('/v1/account/sessions/:sessionId') $appSecret = $project->getAttribute('providers', [])[$provider.'Secret'] ?? '{}'; $className = 'Appwrite\\Auth\\OAuth2\\'.\ucfirst($provider); - + if (!\class_exists($className)) { throw new Exception('Provider is not supported', 501, Exception::PROJECT_PROVIDER_UNSUPPORTED); } @@ -1793,16 +1718,12 @@ App::patch('/v1/account/sessions/:sessionId') $user->setAttribute("sessions", $sessions); $user = $dbForProject->updateDocument('users', $user->getId(), $user); - $audits - ->setParam('userId', $user->getId()) - ->setParam('event', 'account.sessions.update') - ->setParam('resource', 'user/' . $user->getId()) - ; + $audits->setResource('user/' . $user->getId()); $events - ->setPayload($response->output($session, Response::MODEL_SESSION)) ->setParam('userId', $user->getId()) ->setParam('sessionId', $session->getId()) + ->setPayload($response->output($session, Response::MODEL_SESSION)) ; $usage @@ -1843,7 +1764,7 @@ App::delete('/v1/account/sessions') /** @var Utopia\Database\Document $user */ /** @var Utopia\Database\Database $dbForProject */ /** @var Utopia\Locale\Locale $locale */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Event\Event $events */ /** @var Appwrite\Stats\Stats $usage */ @@ -1853,11 +1774,7 @@ App::delete('/v1/account/sessions') foreach ($sessions as $session) {/** @var Document $session */ $dbForProject->deleteDocument('sessions', $session->getId()); - $audits - ->setParam('userId', $user->getId()) - ->setParam('event', 'account.sessions.delete') - ->setParam('resource', 'user/' . $user->getId()) - ; + $audits->setResource('user/' . $user->getId()); if (!Config::getParam('domainVerification')) { $response @@ -1884,18 +1801,19 @@ App::delete('/v1/account/sessions') $numOfSessions = count($sessions); $events + ->setParam('userId', $user->getId()) + ->setParam('sessionId', $session->getId()) ->setPayload($response->output(new Document([ 'sessions' => $sessions, 'total' => $numOfSessions, ]), Response::MODEL_SESSION_LIST)) - ->setParam('userId', $user->getId()) - ->setParam('sessionId', $session->getId()) ; $usage ->setParam('users.sessions.delete', $numOfSessions) ->setParam('users.update', 1) ; + $response->noContent(); }); @@ -1930,8 +1848,8 @@ App::post('/v1/account/recovery') /** @var Utopia\Database\Database $dbForProject */ /** @var Utopia\Database\Document $project */ /** @var Utopia\Locale\Locale $locale */ - /** @var Appwrite\Event\Event $mails */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Mail $mails */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Event\Event $events */ /** @var Appwrite\Stats\Stats $usage */ @@ -1978,40 +1896,33 @@ App::post('/v1/account/recovery') $url = Template::unParseURL($url); $mails - ->setPayload([ - 'from' => $project->getId(), - 'recipient' => $profile->getAttribute('email', ''), - 'name' => $profile->getAttribute('name', ''), - 'url' => $url, - 'locale' => $locale->default, - 'project' => $project->getAttribute('name', ['[APP-NAME]']), - 'type' => MAIL_TYPE_RECOVERY - ]) + ->setType(MAIL_TYPE_RECOVERY) + ->setRecipient($profile->getAttribute('email', '')) + ->setUrl($url) + ->setLocale($locale->default) + ->setName($profile->getAttribute('name')) ->trigger(); ; $events - ->setParam('eventData', - $response->output($recovery->setAttribute('secret', $secret), - Response::MODEL_TOKEN - )) - ->setParam('userId', $profile->getId()) - ->setParam('tokenId', $recovery->getId()) + ->setParam('userId', $profile->getId()) + ->setParam('tokenId', $recovery->getId()) + ->setPayload($response->output( + $recovery->setAttribute('secret', $secret), + Response::MODEL_TOKEN + )) ; $recovery // Hide secret for clients, sp ->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? $secret : ''); - $audits - ->setParam('userId', $profile->getId()) - ->setParam('event', 'account.recovery.create') - ->setParam('resource', 'user/' . $profile->getId()) - ; + $audits->setResource('user/' . $profile->getId()); $usage ->setParam('users.update', 1) ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); $response->dynamic($recovery, Response::MODEL_TOKEN); }); @@ -2042,7 +1953,7 @@ App::put('/v1/account/recovery') ->action(function ($userId, $secret, $password, $passwordAgain, $response, $dbForProject, $audits, $usage, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Stats\Stats $usage */ /** @var Appwrite\Event\Event $events */ @@ -2084,20 +1995,15 @@ App::put('/v1/account/recovery') $dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('tokens', $tokens)); - $audits - ->setParam('userId', $profile->getId()) - ->setParam('event', 'account.recovery.update') - ->setParam('resource', 'user/' . $profile->getId()) - ; + $audits->setResource('user/' . $profile->getId()); - $usage - ->setParam('users.update', 1) - ; + $usage->setParam('users.update', 1); $events ->setParam('userId', $profile->getId()) ->setParam('tokenId', $recovery->getId()) ; + $response->dynamic($recovery, Response::MODEL_TOKEN); }); @@ -2133,9 +2039,9 @@ App::post('/v1/account/verification') /** @var Utopia\Database\Document $user */ /** @var Utopia\Database\Database $dbForProject */ /** @var Utopia\Locale\Locale $locale */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Event\Event $events */ - /** @var Appwrite\Event\Event $mails */ + /** @var Appwrite\Event\Mail $mails */ /** @var Appwrite\Stats\Stats $usage */ if(empty(App::getEnv('_APP_SMTP_HOST'))) { @@ -2171,40 +2077,30 @@ App::post('/v1/account/verification') $url = Template::unParseURL($url); $mails - ->setPayload([ - 'from' => $project->getId(), - 'recipient' => $user->getAttribute('email'), - 'name' => $user->getAttribute('name'), - 'url' => $url, - 'locale' => $locale->default, - 'project' => $project->getAttribute('name', ['[APP-NAME]']), - 'type' => MAIL_TYPE_VERIFICATION - ]) + ->setType(MAIL_TYPE_VERIFICATION) + ->setRecipient($user->getAttribute('email')) + ->setUrl($url) + ->setLocale($locale->default) + ->setName($user->getAttribute('name')) ->trigger() ; $events - ->setParam('eventData', - $response->output($verification->setAttribute('secret', $verificationSecret), - Response::MODEL_TOKEN - )) ->setParam('userId', $user->getId()) ->setParam('tokenId', $verification->getId()) + ->setPayload($response->output( + $verification->setAttribute('secret', $verificationSecret), + Response::MODEL_TOKEN + )) ; $verification // Hide secret for clients, sp ->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? $verificationSecret : ''); - $audits - ->setParam('userId', $user->getId()) - ->setParam('event', 'account.verification.create') - ->setParam('resource', 'user/' . $user->getId()) - ; + $audits->setResource('user/' . $user->getId()); + $usage->setParam('users.update', 1); - $usage - ->setParam('users.update', 1) - ; $response->setStatusCode(Response::STATUS_CODE_CREATED); $response->dynamic($verification, Response::MODEL_TOKEN); }); @@ -2235,7 +2131,7 @@ App::put('/v1/account/verification') /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Document $user */ /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Stats\Stats $usage */ /** @var Appwrite\Event\Event $events */ @@ -2269,15 +2165,9 @@ App::put('/v1/account/verification') $dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('tokens', $tokens)); - $audits - ->setParam('userId', $profile->getId()) - ->setParam('event', 'account.verification.update') - ->setParam('resource', 'user/' . $user->getId()) - ; + $audits->setResource('user/' . $user->getId()); - $usage - ->setParam('users.update', 1) - ; + $usage->setParam('users.update', 1); $events ->setParam('userId', $user->getId()) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 1cf78b07e3..38e3399e82 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -33,6 +33,8 @@ use Appwrite\Network\Validator\URL; use Appwrite\Utopia\Database\Validator\CustomId; use Appwrite\Utopia\Response; use Appwrite\Detector\Detector; +use Appwrite\Event\Audit as EventAudit; +use Appwrite\Event\Database as EventDatabase; use Appwrite\Event\Event; use Appwrite\Stats\Stats; @@ -43,13 +45,13 @@ use Appwrite\Stats\Stats; * @param Utopia\Database\Document $attribute * @param Appwrite\Utopia\Response $response * @param Utopia\Database\Database $dbForProject - * @param Appwrite\Event\Event $database - * @param Appwrite\Event\Event $audits + * @param Appwrite\Event\Database $database + * @param Appwrite\Event\Audit $audits * @param Appwrite\Stats\Stats $usage * * @return Document Newly created attribute document */ -function createAttribute(string $collectionId, Document $attribute, Response $response, Database $dbForProject, Event $database, Event $audits, Stats $usage): Document +function createAttribute(string $collectionId, Document $attribute, Response $response, Database $dbForProject, EventDatabase $database, EventAudit $audits, Event $events, Stats $usage): Document { $key = $attribute->getAttribute('key'); $type = $attribute->getAttribute('type', ''); @@ -113,22 +115,23 @@ function createAttribute(string $collectionId, Document $attribute, Response $re $dbForProject->deleteCachedDocument('collections', $collectionId); $dbForProject->deleteCachedCollection('collection_' . $collection->getInternalId()); - // Pass clone of $attribute object to workers - // so we can later modify Document to fit response model - $clone = clone $attribute; - - $database - ->setParam('type', DATABASE_TYPE_CREATE_ATTRIBUTE) - ->setParam('collection', $collection) - ->setParam('document', $clone) - ; - $usage->setParam('database.collections.update', 1); + $database + ->setType(DATABASE_TYPE_CREATE_ATTRIBUTE) + ->setCollection($collection) + ->setDocument($attribute) + ; + + $events + ->setTrigger($collection) + ->setParam('collectionId', $collection->getId()) + ->setParam('attributeId', $attribute->getId()) + ; + $audits - ->setParam('event', 'database.attributes.create') - ->setParam('resource', 'collection/'.$collectionId) - ->setParam('data', $clone) + ->setResource('collection/'.$collectionId) + ->setPayload($attribute->getArrayCopy()) ; $response->setStatusCode(Response::STATUS_CODE_CREATED); @@ -139,7 +142,7 @@ function createAttribute(string $collectionId, Document $attribute, Response $re App::post('/v1/database/collections') ->desc('Create Collection') ->groups(['api', 'database']) - ->label('event', 'database.collections.create') + ->label('event', 'collections.[collectionId].create') ->label('scope', 'collections.write') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'database') @@ -157,11 +160,13 @@ App::post('/v1/database/collections') ->inject('dbForProject') ->inject('audits') ->inject('usage') - ->action(function ($collectionId, $name, $permission, $read, $write, $response, $dbForProject, $audits, $usage) { + ->inject('events') + ->action(function ($collectionId, $name, $permission, $read, $write, $response, $dbForProject, $audits, $usage, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject*/ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Stats\Stats $usage */ + /** @var Appwrite\Event\Event $events */ $collectionId = $collectionId == 'unique()' ? $dbForProject->getId() : $collectionId; @@ -187,11 +192,11 @@ App::post('/v1/database/collections') } $audits - ->setParam('event', 'database.collections.create') - ->setParam('resource', 'collection/'.$collectionId) - ->setParam('data', $collection->getArrayCopy()) + ->setResource('collection/'.$collectionId) + ->setPayload($collection->getArrayCopy()) ; + $events->setParam('collectionId', $collection->getId()); $usage->setParam('database.collections.create', 1); $response->setStatusCode(Response::STATUS_CODE_CREATED); @@ -581,7 +586,7 @@ App::put('/v1/database/collections/:collectionId') ->desc('Update Collection') ->groups(['api', 'database']) ->label('scope', 'collections.write') - ->label('event', 'database.collections.update') + ->label('event', 'collections.[collectionId].update') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'database') ->label('sdk.method', 'updateCollection') @@ -599,11 +604,13 @@ App::put('/v1/database/collections/:collectionId') ->inject('dbForProject') ->inject('audits') ->inject('usage') - ->action(function ($collectionId, $name, $permission, $read, $write, $enabled, $response, $dbForProject, $audits, $usage) { + ->inject('events') + ->action(function ($collectionId, $name, $permission, $read, $write, $enabled, $response, $dbForProject, $audits, $usage, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Stats\Stats $usage */ + /** @var Appwrite\Event\Event $events */ $collection = $dbForProject->getDocument('collections', $collectionId); @@ -633,14 +640,14 @@ App::put('/v1/database/collections/:collectionId') throw new Exception('Bad structure. '.$exception->getMessage(), 400, Exception::DOCUMENT_INVALID_STRUCTURE); } - $usage->setParam('database.collections.update', 1); - $audits - ->setParam('event', 'database.collections.update') - ->setParam('resource', 'collection/'.$collectionId) - ->setParam('data', $collection->getArrayCopy()) + ->setResource('collection/'.$collectionId) + ->setPayload($collection->getArrayCopy()) ; + $usage->setParam('database.collections.update', 1); + $events->setParam('collectionId', $collection->getId()); + $response->dynamic($collection, Response::MODEL_COLLECTION); }); @@ -648,7 +655,7 @@ App::delete('/v1/database/collections/:collectionId') ->desc('Delete Collection') ->groups(['api', 'database']) ->label('scope', 'collections.write') - ->label('event', 'database.collections.delete') + ->label('event', 'collections.[collectionId].delete') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'database') ->label('sdk.method', 'deleteCollection') @@ -666,8 +673,8 @@ App::delete('/v1/database/collections/:collectionId') /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ /** @var Appwrite\Event\Event $events */ - /** @var Appwrite\Event\Event $audits */ - /** @var Appwrite\Stats\Stats $audits */ + /** @var Appwrite\Event\Audit $audits */ + /** @var Appwrite\Stats\Stats $usage */ $collection = $dbForProject->getDocument('collections', $collectionId); @@ -682,20 +689,22 @@ App::delete('/v1/database/collections/:collectionId') $dbForProject->deleteCachedCollection('collection_' . $collection->getInternalId()); $deletes - ->setParam('type', DELETE_TYPE_DOCUMENT) - ->setParam('document', $collection) + ->setPayload([ + 'type' => DELETE_TYPE_DOCUMENT, + 'document' => $collection + ]) ; $usage->setParam('database.collections.delete', 1); $events - ->setParam('eventData', $response->output($collection, Response::MODEL_COLLECTION)) + ->setParam('collectionId', $collection->getId()) + ->setPayload($response->output($collection, Response::MODEL_COLLECTION)) ; $audits - ->setParam('event', 'database.collections.delete') - ->setParam('resource', 'collection/'.$collectionId) - ->setParam('data', $collection->getArrayCopy()) + ->setResource('collection/'.$collectionId) + ->setPayload($collection->getArrayCopy()) ; $response->noContent(); @@ -704,7 +713,7 @@ App::delete('/v1/database/collections/:collectionId') App::post('/v1/database/collections/:collectionId/attributes/string') ->desc('Create String Attribute') ->groups(['api', 'database']) - ->label('event', 'database.attributes.create') + ->label('event', 'collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'database') @@ -724,12 +733,14 @@ App::post('/v1/database/collections/:collectionId/attributes/string') ->inject('database') ->inject('audits') ->inject('usage') - ->action(function ($collectionId, $key, $size, $required, $default, $array, $response, $dbForProject, $database, $audits, $usage) { + ->inject('events') + ->action(function ($collectionId, $key, $size, $required, $default, $array, $response, $dbForProject, $database, $audits, $usage, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject*/ - /** @var Appwrite\Event\Event $database */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Database $database */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Stats\Stats $usage */ + /** @var Appwrite\Event\Event $events */ // Ensure attribute default is within required size $validator = new Text($size); @@ -744,7 +755,7 @@ App::post('/v1/database/collections/:collectionId/attributes/string') 'required' => $required, 'default' => $default, 'array' => $array, - ]), $response, $dbForProject, $database, $audits, $usage); + ]), $response, $dbForProject, $database, $audits, $events, $usage); $response->dynamic($attribute, Response::MODEL_ATTRIBUTE_STRING); }); @@ -752,7 +763,7 @@ App::post('/v1/database/collections/:collectionId/attributes/string') App::post('/v1/database/collections/:collectionId/attributes/email') ->desc('Create Email Attribute') ->groups(['api', 'database']) - ->label('event', 'database.attributes.create') + ->label('event', 'collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') ->label('sdk.namespace', 'database') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) @@ -771,11 +782,12 @@ App::post('/v1/database/collections/:collectionId/attributes/email') ->inject('database') ->inject('audits') ->inject('usage') - ->action(function ($collectionId, $key, $required, $default, $array, $response, $dbForProject, $database, $audits, $usage) { + ->inject('events') + ->action(function ($collectionId, $key, $required, $default, $array, $response, $dbForProject, $database, $audits, $usage, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject*/ - /** @var Appwrite\Event\Event $database */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Database $database */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Stats\Stats $usage */ $attribute = createAttribute($collectionId, new Document([ @@ -786,7 +798,7 @@ App::post('/v1/database/collections/:collectionId/attributes/email') 'default' => $default, 'array' => $array, 'format' => APP_DATABASE_ATTRIBUTE_EMAIL, - ]), $response, $dbForProject, $database, $audits, $usage); + ]), $response, $dbForProject, $database, $audits, $events, $usage); $response->dynamic($attribute, Response::MODEL_ATTRIBUTE_EMAIL); }); @@ -794,7 +806,7 @@ App::post('/v1/database/collections/:collectionId/attributes/email') App::post('/v1/database/collections/:collectionId/attributes/enum') ->desc('Create Enum Attribute') ->groups(['api', 'database']) - ->label('event', 'database.attributes.create') + ->label('event', 'collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') ->label('sdk.namespace', 'database') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) @@ -814,12 +826,14 @@ App::post('/v1/database/collections/:collectionId/attributes/enum') ->inject('database') ->inject('audits') ->inject('usage') - ->action(function ($collectionId, $key, $elements, $required, $default, $array, $response, $dbForProject, $database, $audits, $usage) { + ->inject('events') + ->action(function ($collectionId, $key, $elements, $required, $default, $array, $response, $dbForProject, $database, $audits, $usage, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject*/ - /** @var Appwrite\Event\Event $database */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Database $database */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Stats\Stats $usage */ + /** @var Appwrite\Event\Event $events */ // use length of longest string as attribute size $size = 0; @@ -845,7 +859,7 @@ App::post('/v1/database/collections/:collectionId/attributes/enum') 'array' => $array, 'format' => APP_DATABASE_ATTRIBUTE_ENUM, 'formatOptions' => ['elements' => $elements], - ]), $response, $dbForProject, $database, $audits, $usage); + ]), $response, $dbForProject, $database, $audits, $events, $usage); $response->dynamic($attribute, Response::MODEL_ATTRIBUTE_ENUM); }); @@ -853,7 +867,7 @@ App::post('/v1/database/collections/:collectionId/attributes/enum') App::post('/v1/database/collections/:collectionId/attributes/ip') ->desc('Create IP Address Attribute') ->groups(['api', 'database']) - ->label('event', 'database.attributes.create') + ->label('event', 'collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') ->label('sdk.namespace', 'database') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) @@ -872,11 +886,12 @@ App::post('/v1/database/collections/:collectionId/attributes/ip') ->inject('database') ->inject('audits') ->inject('usage') - ->action(function ($collectionId, $key, $required, $default, $array, $response, $dbForProject, $database, $audits, $usage) { + ->inject('events') + ->action(function ($collectionId, $key, $required, $default, $array, $response, $dbForProject, $database, $audits, $usage, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject*/ - /** @var Appwrite\Event\Event $database */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Database $database */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Stats\Stats $usage */ $attribute = createAttribute($collectionId, new Document([ @@ -887,7 +902,7 @@ App::post('/v1/database/collections/:collectionId/attributes/ip') 'default' => $default, 'array' => $array, 'format' => APP_DATABASE_ATTRIBUTE_IP, - ]), $response, $dbForProject, $database, $audits, $usage); + ]), $response, $dbForProject, $database, $audits, $events, $usage); $response->dynamic($attribute, Response::MODEL_ATTRIBUTE_IP); }); @@ -895,7 +910,7 @@ App::post('/v1/database/collections/:collectionId/attributes/ip') App::post('/v1/database/collections/:collectionId/attributes/url') ->desc('Create URL Attribute') ->groups(['api', 'database']) - ->label('event', 'database.attributes.create') + ->label('event', 'collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') ->label('sdk.namespace', 'database') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) @@ -914,11 +929,13 @@ App::post('/v1/database/collections/:collectionId/attributes/url') ->inject('database') ->inject('audits') ->inject('usage') - ->action(function ($collectionId, $key, $required, $default, $array, $response, $dbForProject, $database, $audits, $usage) { + ->inject('events') + ->action(function ($collectionId, $key, $required, $default, $array, $response, $dbForProject, $database, $audits, $usage, $events) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Event\Event $database */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Database $database */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Stats\Stats $usage */ + /** @var Appwrite\Event\Event $events */ $attribute = createAttribute($collectionId, new Document([ 'key' => $key, @@ -928,7 +945,7 @@ App::post('/v1/database/collections/:collectionId/attributes/url') 'default' => $default, 'array' => $array, 'format' => APP_DATABASE_ATTRIBUTE_URL, - ]), $response, $dbForProject, $database, $audits, $usage); + ]), $response, $dbForProject, $database, $audits, $events, $usage); $response->dynamic($attribute, Response::MODEL_ATTRIBUTE_URL); }); @@ -936,7 +953,7 @@ App::post('/v1/database/collections/:collectionId/attributes/url') App::post('/v1/database/collections/:collectionId/attributes/integer') ->desc('Create Integer Attribute') ->groups(['api', 'database']) - ->label('event', 'database.attributes.create') + ->label('event', 'collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') ->label('sdk.namespace', 'database') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) @@ -957,12 +974,14 @@ App::post('/v1/database/collections/:collectionId/attributes/integer') ->inject('database') ->inject('audits') ->inject('usage') - ->action(function ($collectionId, $key, $required, $min, $max, $default, $array, $response, $dbForProject, $database, $audits, $usage) { + ->inject('events') + ->action(function ($collectionId, $key, $required, $min, $max, $default, $array, $response, $dbForProject, $database, $audits, $usage, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject*/ - /** @var Appwrite\Event\Event $database */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Database $database */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Stats\Stats $usage */ + /** @var Appwrite\Event\Event $events */ // Ensure attribute default is within range $min = (is_null($min)) ? PHP_INT_MIN : \intval($min); @@ -992,7 +1011,7 @@ App::post('/v1/database/collections/:collectionId/attributes/integer') 'min' => $min, 'max' => $max, ], - ]), $response, $dbForProject, $database, $audits, $usage); + ]), $response, $dbForProject, $database, $audits, $events, $usage); $formatOptions = $attribute->getAttribute('formatOptions', []); @@ -1007,7 +1026,7 @@ App::post('/v1/database/collections/:collectionId/attributes/integer') App::post('/v1/database/collections/:collectionId/attributes/float') ->desc('Create Float Attribute') ->groups(['api', 'database']) - ->label('event', 'database.attributes.create') + ->label('event', 'collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') ->label('sdk.namespace', 'database') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) @@ -1027,13 +1046,15 @@ App::post('/v1/database/collections/:collectionId/attributes/float') ->inject('dbForProject') ->inject('database') ->inject('audits') + ->inject('events') ->inject('usage') - ->action(function ($collectionId, $key, $required, $min, $max, $default, $array, $response, $dbForProject, $database, $audits, $usage) { + ->action(function ($collectionId, $key, $required, $min, $max, $default, $array, $response, $dbForProject, $database, $audits, $events, $usage) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject*/ - /** @var Appwrite\Event\Event $database */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Database $database */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Stats\Stats $usage */ + /** @var Appwrite\Event\Event $events */ // Ensure attribute default is within range $min = (is_null($min)) ? -PHP_FLOAT_MAX : \floatval($min); @@ -1066,7 +1087,7 @@ App::post('/v1/database/collections/:collectionId/attributes/float') 'min' => $min, 'max' => $max, ], - ]), $response, $dbForProject, $database, $audits, $usage); + ]), $response, $dbForProject, $database, $audits, $events, $usage); $formatOptions = $attribute->getAttribute('formatOptions', []); @@ -1081,7 +1102,7 @@ App::post('/v1/database/collections/:collectionId/attributes/float') App::post('/v1/database/collections/:collectionId/attributes/boolean') ->desc('Create Boolean Attribute') ->groups(['api', 'database']) - ->label('event', 'database.attributes.create') + ->label('event', 'collections.[collectionId].attributes.[attributeId].create') ->label('scope', 'collections.write') ->label('sdk.namespace', 'database') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) @@ -1100,12 +1121,14 @@ App::post('/v1/database/collections/:collectionId/attributes/boolean') ->inject('database') ->inject('audits') ->inject('usage') - ->action(function ($collectionId, $key, $required, $default, $array, $response, $dbForProject, $database, $audits, $usage) { + ->inject('events') + ->action(function ($collectionId, $key, $required, $default, $array, $response, $dbForProject, $database, $audits, $usage, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject*/ - /** @var Appwrite\Event\Event $database */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Database $database */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Stats\Stats $usage */ + /** @var Appwrite\Event\Event $events */ $attribute = createAttribute($collectionId, new Document([ 'key' => $key, @@ -1114,7 +1137,7 @@ App::post('/v1/database/collections/:collectionId/attributes/boolean') 'required' => $required, 'default' => $default, 'array' => $array, - ]), $response, $dbForProject, $database, $audits, $usage); + ]), $response, $dbForProject, $database, $audits, $events, $usage); $response->dynamic($attribute, Response::MODEL_ATTRIBUTE_BOOLEAN); }); @@ -1221,7 +1244,7 @@ App::delete('/v1/database/collections/:collectionId/attributes/:key') ->desc('Delete Attribute') ->groups(['api', 'database']) ->label('scope', 'collections.write') - ->label('event', 'database.attributes.delete') + ->label('event', 'collections.[collectionId].attributes.[attributeId].delete') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'database') ->label('sdk.method', 'deleteAttribute') @@ -1239,9 +1262,9 @@ App::delete('/v1/database/collections/:collectionId/attributes/:key') ->action(function ($collectionId, $key, $response, $dbForProject, $database, $events, $audits, $usage) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Event\Event $database */ + /** @var Appwrite\Event\Database $database */ /** @var Appwrite\Event\Event $events */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Stats\Stats $usage */ $collection = $dbForProject->getDocument('collections', $collectionId); @@ -1265,9 +1288,9 @@ App::delete('/v1/database/collections/:collectionId/attributes/:key') $dbForProject->deleteCachedCollection('collection_' . $collection->getInternalId()); $database - ->setParam('type', DATABASE_TYPE_DELETE_ATTRIBUTE) - ->setParam('collection', $collection) - ->setParam('document', $attribute) + ->setType(DATABASE_TYPE_DELETE_ATTRIBUTE) + ->setCollection($collection) + ->setDocument($attribute) ; $usage->setParam('database.collections.update', 1); @@ -1291,13 +1314,15 @@ App::delete('/v1/database/collections/:collectionId/attributes/:key') }; $events - ->setParam('payload', $response->output($attribute, $model)) + ->setParam('collectionId', $collection->getId()) + ->setParam('attributeId', $attribute->getId()) + ->setTrigger($collection) + ->setPayload($response->output($attribute, $model)) ; $audits - ->setParam('event', 'database.attributes.delete') - ->setParam('resource', 'collection/'.$collectionId) - ->setParam('data', $attribute->getArrayCopy()) + ->setResource('collection/'.$collectionId) + ->setPayload($attribute->getArrayCopy()) ; $response->noContent(); @@ -1306,7 +1331,7 @@ App::delete('/v1/database/collections/:collectionId/attributes/:key') App::post('/v1/database/collections/:collectionId/indexes') ->desc('Create Index') ->groups(['api', 'database']) - ->label('event', 'database.indexes.create') + ->label('event', 'collections.[collectionId].indexes.[indexId].create') ->label('scope', 'collections.write') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'database') @@ -1325,12 +1350,14 @@ App::post('/v1/database/collections/:collectionId/indexes') ->inject('database') ->inject('audits') ->inject('usage') - ->action(function ($collectionId, $key, $type, $attributes, $orders, $response, $dbForProject, $database, $audits, $usage) { + ->inject('events') + ->action(function ($collectionId, $key, $type, $attributes, $orders, $response, $dbForProject, $database, $audits, $usage, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Event\Event $database */ - /** @var Appwrite\Event\Event $audits */ - /** @var Appwrite\Stats\Stats $audits */ + /** @var Appwrite\Event\Database $database */ + /** @var Appwrite\Event\Audit $audits */ + /** @var Appwrite\Stats\Stats $usage */ + /** @var Appwrite\Event\Event $events */ $collection = $dbForProject->getDocument('collections', $collectionId); @@ -1393,17 +1420,22 @@ App::post('/v1/database/collections/:collectionId/indexes') $dbForProject->deleteCachedDocument('collections', $collectionId); $database - ->setParam('type', DATABASE_TYPE_CREATE_INDEX) - ->setParam('collection', $collection) - ->setParam('document', $index) + ->setType(DATABASE_TYPE_CREATE_INDEX) + ->setCollection($collection) + ->setDocument($index) ; $usage->setParam('database.collections.update', 1); + $events + ->setParam('collectionId', $collection->getId()) + ->setParam('indexId', $index->getId()) + ->setTrigger($collection) + ; + $audits - ->setParam('event', 'database.indexes.create') - ->setParam('resource', 'collection/'.$collectionId) - ->setParam('data', $index->getArrayCopy()) + ->setResource('collection/'.$collection->getId()) + ->setPayload($index->getArrayCopy()) ; $response->setStatusCode(Response::STATUS_CODE_CREATED); @@ -1493,7 +1525,7 @@ App::delete('/v1/database/collections/:collectionId/indexes/:key') ->desc('Delete Index') ->groups(['api', 'database']) ->label('scope', 'collections.write') - ->label('event', 'database.indexes.delete') + ->label('event', 'collections.[collectionId].indexes.[indexId].delete') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'database') ->label('sdk.method', 'deleteIndex') @@ -1511,9 +1543,9 @@ App::delete('/v1/database/collections/:collectionId/indexes/:key') ->action(function ($collectionId, $key, $response, $dbForProject, $database, $events, $audits, $usage) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Event\Event $database */ + /** @var Appwrite\Event\Database $database */ /** @var Appwrite\Event\Event $events */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Stats\Stats $usage */ $collection = $dbForProject->getDocument('collections', $collectionId); @@ -1536,21 +1568,23 @@ App::delete('/v1/database/collections/:collectionId/indexes/:key') $dbForProject->deleteCachedDocument('collections', $collectionId); $database - ->setParam('type', DATABASE_TYPE_DELETE_INDEX) - ->setParam('collection', $collection) - ->setParam('document', $index) + ->setType(DATABASE_TYPE_DELETE_INDEX) + ->setCollection($collection) + ->setDocument($index) ; $usage->setParam('database.collections.update', 1); $events - ->setParam('payload', $response->output($index, Response::MODEL_INDEX)) + ->setParam('collectionId', $collection->getId()) + ->setParam('indexId', $index->getId()) + ->setTrigger($collection) + ->setPayload($response->output($index, Response::MODEL_INDEX)) ; $audits - ->setParam('event', 'database.indexes.delete') - ->setParam('resource', 'collection/'.$collectionId) - ->setParam('data', $index->getArrayCopy()) + ->setResource('collection/'.$collection->getId()) + ->setPayload($index->getArrayCopy()) ; $response->noContent(); @@ -1559,7 +1593,7 @@ App::delete('/v1/database/collections/:collectionId/indexes/:key') App::post('/v1/database/collections/:collectionId/documents') ->desc('Create Document') ->groups(['api', 'database']) - ->label('event', 'database.documents.create') + ->label('event', 'collections.[collectionId].documents.[documentId].create') ->label('scope', 'documents.write') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'database') @@ -1584,7 +1618,7 @@ App::post('/v1/database/collections/:collectionId/documents') /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ /** @var Utopia\Database\Document $user */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Stats\Stats $usage */ /** @var Appwrite\Event\Event $events */ /** @var string $mode */ @@ -1658,7 +1692,11 @@ App::post('/v1/database/collections/:collectionId/documents') throw new Exception('Document already exists', 409, Exception::DOCUMENT_ALREADY_EXISTS); } - $events->setParam('collection', $collection->getArrayCopy()); + $events + ->setParam('collectionId', $collection->getId()) + ->setParam('documentId', $document->getId()) + ->setTrigger($collection) + ; $usage ->setParam('database.documents.create', 1) @@ -1666,9 +1704,8 @@ App::post('/v1/database/collections/:collectionId/documents') ; $audits - ->setParam('event', 'database.documents.create') - ->setParam('resource', 'document/'.$document->getId()) - ->setParam('data', $document->getArrayCopy()) + ->setResource('document/'.$document->getId()) + ->setPayload($document->getArrayCopy()) ; $response->setStatusCode(Response::STATUS_CODE_CREATED); @@ -1938,7 +1975,7 @@ App::get('/v1/database/collections/:collectionId/documents/:documentId/logs') App::patch('/v1/database/collections/:collectionId/documents/:documentId') ->desc('Update Document') ->groups(['api', 'database']) - ->label('event', 'database.documents.update') + ->label('event', 'collections.[collectionId].documents.[documentId].update') ->label('scope', 'documents.write') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'database') @@ -1961,7 +1998,7 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId') ->action(function ($collectionId, $documentId, $data, $read, $write, $response, $dbForProject, $audits, $usage, $events, $mode) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Stats\Stats $usage */ /** @var Appwrite\Event\Event $events */ /** @var string $mode */ @@ -1999,7 +2036,7 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId') if (empty($data)) { throw new Exception('Missing payload', 400, Exception::DOCUMENT_MISSING_PAYLOAD); } - + if (!\is_array($data)) { throw new Exception('Data param should be a valid JSON object', 400, Exception::DOCUMENT_INVALID_STRUCTURE); } @@ -2053,17 +2090,20 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId') throw new Exception($exception->getMessage(), 400, Exception::DOCUMENT_INVALID_STRUCTURE); } - $events->setParam('collection', $collection->getArrayCopy()); + $events + ->setParam('collectionId', $collection->getId()) + ->setParam('documentId', $document->getId()) + ->setTrigger($collection) + ; $usage ->setParam('database.documents.update', 1) ->setParam('collectionId', $collectionId) - ; + ; $audits - ->setParam('event', 'database.documents.update') - ->setParam('resource', 'document/'.$document->getId()) - ->setParam('data', $document->getArrayCopy()) + ->setResource('document/'.$document->getId()) + ->setPayload($document->getArrayCopy()) ; $response->dynamic($document, Response::MODEL_DOCUMENT); @@ -2073,7 +2113,7 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId') ->desc('Delete Document') ->groups(['api', 'database']) ->label('scope', 'documents.write') - ->label('event', 'database.documents.delete') + ->label('event', 'collections.[collectionId].documents.[documentId].delete') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'database') ->label('sdk.method', 'deleteDocument') @@ -2093,7 +2133,7 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId') /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ /** @var Appwrite\Event\Event $events */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Event\Event $deletes */ /** @var Appwrite\Stats\Stats $usage */ /** @var string $mode */ @@ -2142,24 +2182,27 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId') $document->setAttribute('$collection', $collectionId); $deletes - ->setParam('type', DELETE_TYPE_AUDIT) - ->setParam('document', $document) + ->setPayload([ + 'type' => DELETE_TYPE_AUDIT, + 'document' => $document + ]) ; $usage ->setParam('database.documents.delete', 1) ->setParam('collectionId', $collectionId) - ; + ; $events - ->setParam('eventData', $response->output($document, Response::MODEL_DOCUMENT)) - ->setParam('collection', $collection->getArrayCopy()); + ->setParam('collectionId', $collection->getId()) + ->setParam('documentId', $document->getId()) + ->setTrigger($collection) + ->setPayload($response->output($document, Response::MODEL_DOCUMENT)) ; $audits - ->setParam('event', 'database.documents.delete') - ->setParam('resource', 'document/'.$document->getId()) - ->setParam('data', $document->getArrayCopy()) // Audit document in case of malicious or disastrous action + ->setResource('document/'.$document->getId()) + ->setPayload($document->getArrayCopy()) ; $response->noContent(); diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index d491b7d75b..ee19a737f6 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -3,6 +3,7 @@ use Ahc\Jwt\JWT; use Appwrite\Auth\Auth; use Appwrite\Event\Event; +use Appwrite\Event\Validator\Event as ValidatorEvent; use Appwrite\Extend\Exception; use Appwrite\Utopia\Database\Validator\CustomId; use Utopia\Database\Validator\UID; @@ -34,7 +35,7 @@ App::post('/v1/functions') ->groups(['api', 'functions']) ->desc('Create Function') ->label('scope', 'functions.write') - ->label('event', 'functions.create') + ->label('event', 'functions.[functionId].create') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'create') @@ -47,14 +48,16 @@ App::post('/v1/functions') ->param('execute', [], new ArrayList(new Text(64)), 'An array of strings with execution permissions. By default no user is granted with any execute permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.') ->param('runtime', '', new WhiteList(array_keys(Config::getParam('runtimes')), true), 'Execution runtime.') ->param('vars', [], new Assoc(), 'Key-value JSON object that will be passed to the function as environment variables.', true) - ->param('events', [], new ArrayList(new WhiteList(array_keys(Config::getParam('events')), true)), 'Events list.', true) + ->param('events', [], new ArrayList(new ValidatorEvent()), 'Events list.', true) ->param('schedule', '', new Cron(), 'Schedule CRON syntax.', true) ->param('timeout', 15, new Range(1, (int) App::getEnv('_APP_FUNCTIONS_TIMEOUT', 900)), 'Function maximum execution time in seconds.', true) ->inject('response') ->inject('dbForProject') - ->action(function ($functionId, $name, $execute, $runtime, $vars, $events, $schedule, $timeout, $response, $dbForProject) { + ->inject('events') + ->action(function ($functionId, $name, $execute, $runtime, $vars, $events, $schedule, $timeout, $response, $dbForProject, $eventsInstance) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ + /** @var Appwrite\Event\Event $eventsInstance */ $functionId = ($functionId == 'unique()') ? $dbForProject->getId() : $functionId; $function = $dbForProject->createDocument('functions', new Document([ @@ -75,6 +78,8 @@ App::post('/v1/functions') 'search' => implode(' ', [$functionId, $name, $runtime]), ])); + $eventsInstance->setParam('functionId', $function->getId()); + $response->setStatusCode(Response::STATUS_CODE_CREATED); $response->dynamic($function, Response::MODEL_FUNCTION); }); @@ -144,7 +149,7 @@ App::get('/v1/functions/runtimes') return $runtimes[$key]; }, array_keys($runtimes)); - $response->dynamic(new Document([ + $response->dynamic(new Document([ 'total' => count($runtimes), 'runtimes' => $runtimes ]), Response::MODEL_RUNTIME_LIST); @@ -202,9 +207,9 @@ App::get('/v1/functions/:functionId/usage') if ($function->isEmpty()) { throw new Exception('Function not found', 404, Exception::FUNCTION_NOT_FOUND); } - + $usage = []; - if(App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') { + if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') { $periods = [ '24h' => [ 'period' => '30m', @@ -223,16 +228,16 @@ App::get('/v1/functions/:functionId/usage') 'limit' => 90, ], ]; - + $metrics = [ - "functions.$functionId.executions", - "functions.$functionId.failures", + "functions.$functionId.executions", + "functions.$functionId.failures", "functions.$functionId.compute" ]; $stats = []; - Authorization::skip(function() use ($dbForProject, $periods, $range, $metrics, &$stats) { + Authorization::skip(function () use ($dbForProject, $periods, $range, $metrics, &$stats) { foreach ($metrics as $metric) { $limit = $periods[$range]['limit']; $period = $periods[$range]['period']; @@ -241,7 +246,7 @@ App::get('/v1/functions/:functionId/usage') new Query('period', Query::TYPE_EQUAL, [$period]), new Query('metric', Query::TYPE_EQUAL, [$metric]), ], $limit, 0, ['time'], [Database::ORDER_DESC]); - + $stats[$metric] = []; foreach ($requestDocs as $requestDoc) { $stats[$metric][] = [ @@ -254,7 +259,7 @@ App::get('/v1/functions/:functionId/usage') $backfill = $limit - \count($requestDocs); while ($backfill > 0) { $last = $limit - $backfill - 1; // array index of last added metric - $diff = match($period) { // convert period to seconds for unix timestamp math + $diff = match ($period) { // convert period to seconds for unix timestamp math '30m' => 1800, '1d' => 86400, }; @@ -265,7 +270,7 @@ App::get('/v1/functions/:functionId/usage') $backfill--; } $stats[$metric] = array_reverse($stats[$metric]); - } + } }); $usage = new Document([ @@ -283,7 +288,7 @@ App::put('/v1/functions/:functionId') ->groups(['api', 'functions']) ->desc('Update Function') ->label('scope', 'functions.write') - ->label('event', 'functions.update') + ->label('event', 'functions.[functionId].update') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'update') @@ -295,18 +300,20 @@ App::put('/v1/functions/:functionId') ->param('name', '', new Text(128), 'Function name. Max length: 128 chars.') ->param('execute', [], new ArrayList(new Text(64)), 'An array of strings with execution permissions. By default no user is granted with any execute permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.') ->param('vars', [], new Assoc(), 'Key-value JSON object that will be passed to the function as environment variables.', true) - ->param('events', [], new ArrayList(new WhiteList(array_keys(Config::getParam('events')), true)), 'Events list.', true) + ->param('events', [], new ArrayList(new ValidatorEvent()), 'Events list.', true) ->param('schedule', '', new Cron(), 'Schedule CRON syntax.', true) ->param('timeout', 15, new Range(1, (int) App::getEnv('_APP_FUNCTIONS_TIMEOUT', 900)), 'Maximum execution time in seconds.', true) ->inject('response') ->inject('dbForProject') ->inject('project') ->inject('user') - ->action(function ($functionId, $name, $execute, $vars, $events, $schedule, $timeout, $response, $dbForProject, $project, $user) { + ->inject('events') + ->action(function ($functionId, $name, $execute, $vars, $events, $schedule, $timeout, $response, $dbForProject, $project, $user, $eventsInstance) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ /** @var Utopia\Database\Document $project */ /** @var Appwrite\Auth\User $user */ + /** @var Appwrite\Event\Event $eventsInstance */ $function = $dbForProject->getDocument('functions', $functionId); @@ -341,6 +348,8 @@ App::put('/v1/functions/:functionId') ]); // Async task rescheduale } + $eventsInstance->setParam('functionId', $function->getId()); + $response->dynamic($function, Response::MODEL_FUNCTION); }); @@ -348,7 +357,7 @@ App::patch('/v1/functions/:functionId/deployments/:deploymentId') ->groups(['api', 'functions']) ->desc('Update Function Deployment') ->label('scope', 'functions.write') - ->label('event', 'functions.deployments.update') + ->label('event', 'functions.[functionId].deployments.[deploymentId].update') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'updateDeployment') @@ -361,10 +370,12 @@ App::patch('/v1/functions/:functionId/deployments/:deploymentId') ->inject('response') ->inject('dbForProject') ->inject('project') - ->action(function ($functionId, $deploymentId, $response, $dbForProject, $project) { + ->inject('events') + ->action(function ($functionId, $deploymentId, $response, $dbForProject, $project, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ /** @var Utopia\Database\Document $project */ + /** @var Appwrite\Event\Event $events */ $function = $dbForProject->getDocument('functions', $functionId); $deployment = $dbForProject->getDocument('deployments', $deploymentId); @@ -404,6 +415,11 @@ App::patch('/v1/functions/:functionId/deployments/:deploymentId') 'trigger' => 'schedule', ]); // Async task rescheduale } + + $events + ->setParam('functionId', $function->getId()) + ->setParam('deploymentId', $deployment->getId()); + $response->dynamic($function, Response::MODEL_FUNCTION); }); @@ -411,7 +427,7 @@ App::delete('/v1/functions/:functionId') ->groups(['api', 'functions']) ->desc('Delete Function') ->label('scope', 'functions.write') - ->label('event', 'functions.delete') + ->label('event', 'functions.[functionId].delete') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'delete') @@ -422,10 +438,12 @@ App::delete('/v1/functions/:functionId') ->inject('response') ->inject('dbForProject') ->inject('deletes') - ->action(function ($functionId, $response, $dbForProject, $deletes) { + ->inject('events') + ->action(function ($functionId, $response, $dbForProject, $deletes, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ /** @var Appwrite\Event\Event $deletes */ + /** @var Appwrite\Event\Event $events */ $function = $dbForProject->getDocument('functions', $functionId); @@ -437,10 +455,12 @@ App::delete('/v1/functions/:functionId') throw new Exception('Failed to remove function from DB', 500, Exception::GENERAL_SERVER_ERROR); } - $deletes - ->setParam('type', DELETE_TYPE_DOCUMENT) - ->setParam('document', $function) - ; + $deletes->setPayload([ + 'type' => DELETE_TYPE_DOCUMENT, + 'document' => $function + ]); + + $events->setParam('functionId', $function->getId()); $response->noContent(); }); @@ -449,7 +469,7 @@ App::post('/v1/functions/:functionId/deployments') ->groups(['api', 'functions']) ->desc('Create Deployment') ->label('scope', 'functions.write') - ->label('event', 'functions.deployments.create') + ->label('event', 'functions.[functionId].deployments.[deploymentId].create') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'createDeployment') @@ -467,16 +487,16 @@ App::post('/v1/functions/:functionId/deployments') ->inject('response') ->inject('dbForProject') ->inject('usage') - ->inject('user') + ->inject('events') ->inject('project') ->inject('deviceFunctions') ->inject('deviceLocal') - ->action(function ($functionId, $entrypoint, $file, $activate, $request, $response, $dbForProject, $usage, $user, $project, $deviceFunctions, $deviceLocal) { + ->action(function ($functionId, $entrypoint, $file, $activate, $request, $response, $dbForProject, $usage, $events, $project, $deviceFunctions, $deviceLocal) { /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ /** @var Appwrite\Event\Event $usage */ - /** @var Appwrite\Auth\User $user */ + /** @var Appwrite\Event\Event $events */ /** @var Appwrite\Database\Document $project */ /** @var Utopia\Storage\Device $deviceFunctions */ /** @var Utopia\Storage\Device $deviceLocal */ @@ -515,7 +535,7 @@ App::post('/v1/functions/:functionId/deployments') $end = $request->getContentRangeEnd(); $fileSize = $request->getContentRangeSize(); $deploymentId = $request->getHeader('x-appwrite-id', $deploymentId); - if(is_null($start) || is_null($end) || is_null($fileSize)) { + if (is_null($start) || is_null($end) || is_null($fileSize)) { throw new Exception('Invalid content-range header', 400, Exception::STORAGE_INVALID_CONTENT_RANGE); } @@ -539,8 +559,8 @@ App::post('/v1/functions/:functionId/deployments') // Save to storage $fileSize ??= $deviceLocal->getFileSize($fileTmpName); - $path = $deviceFunctions->getPath($deploymentId.'.'.\pathinfo($fileName, PATHINFO_EXTENSION)); - + $path = $deviceFunctions->getPath($deploymentId . '.' . \pathinfo($fileName, PATHINFO_EXTENSION)); + $deployment = $dbForProject->getDocument('deployments', $deploymentId); $metadata = ['content_type' => $deviceLocal->getFileMimeType($fileTmpName)]; @@ -560,7 +580,7 @@ App::post('/v1/functions/:functionId/deployments') $activate = (bool) filter_var($activate, FILTER_VALIDATE_BOOLEAN); - if($chunksUploaded === $chunks) { + if ($chunksUploaded === $chunks) { if ($activate) { // Remove deploy for all other deployments. $activeDeployments = $dbForProject->find('deployments', [ @@ -574,7 +594,7 @@ App::post('/v1/functions/:functionId/deployments') $dbForProject->updateDocument('deployments', $activeDeployment->getId(), $activeDeployment); } } - + $fileSize = $deviceFunctions->getFileSize($path); if ($deployment->isEmpty()) { @@ -604,11 +624,9 @@ App::post('/v1/functions/:functionId/deployments') 'type' => BUILD_TYPE_DEPLOYMENT ]); - $usage - ->setParam('storage', $deployment->getAttribute('size', 0)) - ; + $usage->setParam('storage', $deployment->getAttribute('size', 0)); } else { - if($deployment->isEmpty()) { + if ($deployment->isEmpty()) { $deployment = $dbForProject->createDocument('deployments', new Document([ '$id' => $deploymentId, '$read' => ['role:all'], @@ -632,6 +650,10 @@ App::post('/v1/functions/:functionId/deployments') $metadata = null; + $events + ->setParam('functionId', $function->getId()) + ->setParam('deploymentId', $deployment->getId()); + $response->setStatusCode(Response::STATUS_CODE_CREATED); $response->dynamic($deployment, Response::MODEL_DEPLOYMENT); }); @@ -742,7 +764,7 @@ App::delete('/v1/functions/:functionId/deployments/:deploymentId') ->groups(['api', 'functions']) ->desc('Delete Deployment') ->label('scope', 'functions.write') - ->label('event', 'functions.deployments.delete') + ->label('event', 'functions.[functionId].deployments.[deploymentId].delete') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'deleteDeployment') @@ -755,19 +777,21 @@ App::delete('/v1/functions/:functionId/deployments/:deploymentId') ->inject('dbForProject') ->inject('usage') ->inject('deletes') + ->inject('events') ->inject('deviceFunctions') - ->action(function ($functionId, $deploymentId, $response, $dbForProject, $usage, $deletes, $deviceFunctions) { + ->action(function ($functionId, $deploymentId, $response, $dbForProject, $usage, $deletes, $events, $deviceFunctions) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ /** @var Appwrite\Event\Event $usage */ /** @var Appwrite\Event\Event $deletes */ + /** @var Appwrite\Event\Event $events */ /** @var Utopia\Storage\Device $deviceFunctions */ $function = $dbForProject->getDocument('functions', $functionId); if ($function->isEmpty()) { throw new Exception('Function not found', 404, Exception::FUNCTION_NOT_FOUND); } - + $deployment = $dbForProject->getDocument('deployments', $deploymentId); if ($deployment->isEmpty()) { throw new Exception('Deployment not found', 404, Exception::DEPLOYMENT_NOT_FOUND); @@ -783,20 +807,24 @@ App::delete('/v1/functions/:functionId/deployments/:deploymentId') } } - if($function->getAttribute('deployment') === $deployment->getId()) { // Reset function deployment + if ($function->getAttribute('deployment') === $deployment->getId()) { // Reset function deployment $function = $dbForProject->updateDocument('functions', $function->getId(), new Document(array_merge($function->getArrayCopy(), [ 'deployment' => '', ]))); } $usage - ->setParam('storage', $deployment->getAttribute('size', 0) * -1) - ; + ->setParam('storage', $deployment->getAttribute('size', 0) * -1); + + $events + ->setParam('functionId', $function->getId()) + ->setParam('deploymentId', $deployment->getId()); $deletes - ->setParam('type', DELETE_TYPE_DOCUMENT) - ->setParam('document', $deployment) - ; + ->setPayload([ + 'type' => DELETE_TYPE_DOCUMENT, + 'document' => $deployment + ]); $response->noContent(); }); @@ -805,7 +833,7 @@ App::post('/v1/functions/:functionId/executions') ->groups(['api', 'functions']) ->desc('Create Execution') ->label('scope', 'execution.write') - ->label('event', 'functions.executions.create') + ->label('event', 'functions.[functionId].executions.[executionId].create') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'createExecution') @@ -822,13 +850,15 @@ App::post('/v1/functions/:functionId/executions') ->inject('project') ->inject('dbForProject') ->inject('user') - ->action(function ($functionId, $data, $async, $response, $project, $dbForProject, $user) { + ->inject('events') + ->action(function ($functionId, $data, $async, $response, $project, $dbForProject, $user, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Document $project */ /** @var Utopia\Database\Database $dbForProject */ /** @var Utopia\Database\Document $user */ + /** @var Appwrite\Event\Event $events */ - $function = Authorization::skip(fn() => $dbForProject->getDocument('functions', $functionId)); + $function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId)); if ($function->isEmpty()) { throw new Exception('Function not found', 404, Exception::FUNCTION_NOT_FOUND); @@ -842,7 +872,7 @@ App::post('/v1/functions/:functionId/executions') throw new Exception('Runtime "' . $function->getAttribute('runtime', '') . '" is not supported', 400, Exception::FUNCTION_RUNTIME_UNSUPPORTED); } - $deployment = Authorization::skip(fn() => $dbForProject->getDocument('deployments', $function->getAttribute('deployment', ''))); + $deployment = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $function->getAttribute('deployment', ''))); if ($deployment->getAttribute('resourceId') !== $function->getId()) { throw new Exception('Deployment not found. Deploy deployment before trying to execute a function', 404, Exception::DEPLOYMENT_NOT_FOUND); @@ -853,7 +883,7 @@ App::post('/v1/functions/:functionId/executions') } /** Check if build has completed */ - $build = Authorization::skip(fn() => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''))); + $build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''))); if ($build->isEmpty()) { throw new Exception('Build not found', 404, Exception::BUILD_NOT_FOUND); } @@ -870,7 +900,7 @@ App::post('/v1/functions/:functionId/executions') $executionId = $dbForProject->getId(); - $execution = Authorization::skip(fn() => $dbForProject->createDocument('executions', new Document([ + $execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', new Document([ '$id' => $executionId, '$read' => (!$user->isEmpty()) ? ['user:' . $user->getId()] : [], '$write' => [], @@ -892,13 +922,14 @@ App::post('/v1/functions/:functionId/executions') $sessions = $user->getAttribute('sessions', []); $current = new Document(); - foreach ($sessions as $session) { /** @var Utopia\Database\Document $session */ + foreach ($sessions as $session) { + /** @var Utopia\Database\Document $session */ if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too $current = $session; } } - if(!$current->isEmpty()) { + if (!$current->isEmpty()) { $jwtObj = new JWT(App::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 10); // Instantiate with key, algo, maxAge and leeway. $jwt = $jwtObj->encode([ 'userId' => $user->getId(), @@ -907,19 +938,30 @@ App::post('/v1/functions/:functionId/executions') } } - if ($async) { - Resque::enqueue(Event::FUNCTIONS_QUEUE_NAME, Event::FUNCTIONS_CLASS_NAME, [ - 'projectId' => $project->getId(), - 'functionId' => $function->getId(), - 'webhooks' => $project->getAttribute('webhooks', []), - 'executionId' => $execution->getId(), - 'trigger' => 'http', + $events + ->setParam('functionId', $function->getId()) + ->setParam('executionId', $execution->getId()) + ->setPayload([ 'data' => $data, - 'userId' => $user->getId(), - 'jwt' => $jwt, - ]); + 'jwt' => $jwt + ]) + ->setTrigger($execution); + + if ($async) { + $event = new Event(Event::FUNCTIONS_QUEUE_NAME, Event::FUNCTIONS_CLASS_NAME); + $event + ->setProject($project) + ->setUser($user) + ->setTrigger($execution) + ->setPayload([ + 'data' => $data, + 'jwt' => $jwt + ]) + ->trigger(); + $response->setStatusCode(Response::STATUS_CODE_CREATED); + return $response->dynamic($execution, Response::MODEL_EXECUTION); } @@ -966,7 +1008,7 @@ App::post('/v1/functions/:functionId/executions') Console::error($th->getMessage()); } - Authorization::skip(fn() => $dbForProject->updateDocument('executions', $executionId, $execution)); + Authorization::skip(fn () => $dbForProject->updateDocument('executions', $executionId, $execution)); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -996,7 +1038,7 @@ App::get('/v1/functions/:functionId/executions') /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ - $function = Authorization::skip(fn() => $dbForProject->getDocument('functions', $functionId)); + $function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId)); if ($function->isEmpty()) { throw new Exception('Function not found', 404, Exception::FUNCTION_NOT_FOUND); @@ -1046,7 +1088,7 @@ App::get('/v1/functions/:functionId/executions/:executionId') /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ - $function = Authorization::skip(fn() => $dbForProject->getDocument('functions', $functionId)); + $function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId)); if ($function->isEmpty()) { throw new Exception('Function not found', 404, Exception::FUNCTION_NOT_FOUND); @@ -1069,7 +1111,7 @@ App::post('/v1/functions/:functionId/deployments/:deploymentId/builds/:buildId') ->groups(['api', 'functions']) ->desc('Retry Build') ->label('scope', 'functions.write') - ->label('event', 'functions.deployments.update') + ->label('event', 'functions.[functionId].deployments.[deploymentId].update') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'retryBuild') @@ -1082,10 +1124,12 @@ App::post('/v1/functions/:functionId/deployments/:deploymentId/builds/:buildId') ->inject('response') ->inject('dbForProject') ->inject('project') - ->action(function ($functionId, $deploymentId, $buildId, $response, $dbForProject, $project) { + ->inject('events') + ->action(function ($functionId, $deploymentId, $buildId, $response, $dbForProject, $project, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ /** @var Utopia\Database\Document $project */ + /** @var Appwrite\Event\Event $events */ $function = $dbForProject->getDocument('functions', $functionId); $deployment = $dbForProject->getDocument('deployments', $deploymentId); @@ -1098,7 +1142,7 @@ App::post('/v1/functions/:functionId/deployments/:deploymentId/builds/:buildId') throw new Exception('Deployment not found', 404, Exception::DEPLOYMENT_NOT_FOUND); } - $build = Authorization::skip(fn() => $dbForProject->getDocument('builds', $buildId)); + $build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $buildId)); if ($build->isEmpty()) { throw new Exception('Build not found', 404, Exception::BUILD_NOT_FOUND); @@ -1108,6 +1152,10 @@ App::post('/v1/functions/:functionId/deployments/:deploymentId/builds/:buildId') throw new Exception('Build not failed', 400, Exception::BUILD_IN_PROGRESS); } + $events + ->setParam('functionId', $function->getId()) + ->setParam('deploymentId', $deployment->getId()); + // Enqueue a message to start the build Resque::enqueue(Event::BUILDS_QUEUE_NAME, Event::BUILDS_CLASS_NAME, [ 'projectId' => $project->getId(), @@ -1117,4 +1165,4 @@ App::post('/v1/functions/:functionId/deployments/:deploymentId/builds/:buildId') ]); $response->noContent(); - }); \ No newline at end of file + }); diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index fb5b930b01..c7aa50a891 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -2,6 +2,7 @@ use Appwrite\Auth\Auth; use Appwrite\Auth\Validator\Password; +use Appwrite\Event\Certificate; use Appwrite\Event\Validator\Event; use Appwrite\Network\Validator\CNAME; use Appwrite\Network\Validator\Domain as DomainValidator; @@ -557,8 +558,10 @@ App::delete('/v1/projects/:projectId') } $deletes - ->setParam('type', DELETE_TYPE_DOCUMENT) - ->setParam('document', $project) + ->setPayload([ + 'type' => DELETE_TYPE_DOCUMENT, + 'document' => $project + ]) ; if (!$dbForConsole->deleteDocument('teams', $project->getAttribute('teamId', null))) { @@ -1390,10 +1393,12 @@ App::patch('/v1/projects/:projectId/domains/:domainId/verification') $dbForConsole->deleteCachedDocument('projects', $project->getId()); // Issue a TLS certificate when domain is verified - Resque::enqueue('v1-certificates', 'CertificatesV1', [ - 'document' => $domain->getArrayCopy(), - 'domain' => $domain->getAttribute('domain'), - ]); + $event = new Certificate(); + $event + ->setDomain($domain) + ->setValidateCNAME(true) + ->setValidateTarget(true) + ->trigger(); $response->dynamic($domain, Response::MODEL_DOMAIN); }); @@ -1436,8 +1441,10 @@ App::delete('/v1/projects/:projectId/domains/:domainId') $dbForConsole->deleteCachedDocument('projects', $project->getId()); $deletes - ->setParam('type', DELETE_TYPE_CERTIFICATES) - ->setParam('document', $domain) + ->setPayload([ + 'type' => DELETE_TYPE_CERTIFICATES, + 'document' => $domain + ]) ; $response->noContent(); diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 97e82b553d..ffc410274f 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -39,7 +39,7 @@ App::post('/v1/storage/buckets') ->desc('Create bucket') ->groups(['api', 'storage']) ->label('scope', 'buckets.write') - ->label('event', 'storage.buckets.create') + ->label('event', 'buckets.[bucketId].create') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'storage') ->label('sdk.method', 'createBucket') @@ -61,11 +61,13 @@ App::post('/v1/storage/buckets') ->inject('dbForProject') ->inject('audits') ->inject('usage') - ->action(function ($bucketId, $name, $permission, $read, $write, $enabled, $maximumFileSize, $allowedFileExtensions, $encryption, $antivirus, $response, $dbForProject, $audits, $usage) { + ->inject('events') + ->action(function ($bucketId, $name, $permission, $read, $write, $enabled, $maximumFileSize, $allowedFileExtensions, $encryption, $antivirus, $response, $dbForProject, $audits, $usage, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Stats\Stats $usage */ + /** @var Appwrite\Event\Event $events */ $bucketId = $bucketId === 'unique()' ? $dbForProject->getId() : $bucketId; try { @@ -124,9 +126,12 @@ App::post('/v1/storage/buckets') } $audits - ->setParam('event', 'storage.buckets.create') - ->setParam('resource', 'storage/buckets/' . $bucket->getId()) - ->setParam('data', $bucket->getArrayCopy()) + ->setResource('storage/buckets/' . $bucket->getId()) + ->setPayload($bucket->getArrayCopy()) + ; + + $events + ->setParam('bucketId', $bucket->getId()) ; $usage->setParam('storage.buckets.create', 1); @@ -213,7 +218,7 @@ App::put('/v1/storage/buckets/:bucketId') ->desc('Update Bucket') ->groups(['api', 'storage']) ->label('scope', 'buckets.write') - ->label('event', 'storage.buckets.update') + ->label('event', 'buckets.[bucketId].update') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'storage') ->label('sdk.method', 'updateBucket') @@ -235,11 +240,13 @@ App::put('/v1/storage/buckets/:bucketId') ->inject('dbForProject') ->inject('audits') ->inject('usage') - ->action(function ($bucketId, $name, $permission, $read, $write, $enabled, $maximumFileSize, $allowedFileExtensions, $encryption, $antivirus, $response, $dbForProject, $audits, $usage) { + ->inject('events') + ->action(function ($bucketId, $name, $permission, $read, $write, $enabled, $maximumFileSize, $allowedFileExtensions, $encryption, $antivirus, $response, $dbForProject, $audits, $usage, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Stats\Stats $usage */ + /** @var Appwrite\Event\Event $events */ $bucket = $dbForProject->getDocument('buckets', $bucketId); @@ -247,13 +254,13 @@ App::put('/v1/storage/buckets/:bucketId') throw new Exception('Bucket not found', 404, Exception::STORAGE_BUCKET_NOT_FOUND); } - $read??=$bucket->getAttribute('$read', []); // By default inherit read permissions - $write??=$bucket->getAttribute('$write', []); // By default inherit write permissions - $maximumFileSize??=$bucket->getAttribute('maximumFileSize', (int) App::getEnv('_APP_STORAGE_LIMIT', 0)); - $allowedFileExtensions??=$bucket->getAttribute('allowedFileExtensions', []); - $enabled??=$bucket->getAttribute('enabled', true); - $encryption??=$bucket->getAttribute('encryption', true); - $antivirus??=$bucket->getAttribute('antivirus', true); + $read ??= $bucket->getAttribute('$read', []); // By default inherit read permissions + $write ??= $bucket->getAttribute('$write', []); // By default inherit write permissions + $maximumFileSize ??= $bucket->getAttribute('maximumFileSize', (int) App::getEnv('_APP_STORAGE_LIMIT', 0)); + $allowedFileExtensions ??= $bucket->getAttribute('allowedFileExtensions', []); + $enabled ??= $bucket->getAttribute('enabled', true); + $encryption ??= $bucket->getAttribute('encryption', true); + $antivirus ??= $bucket->getAttribute('antivirus', true); $bucket = $dbForProject->updateDocument('buckets', $bucket->getId(), $bucket ->setAttribute('name', $name) @@ -268,9 +275,12 @@ App::put('/v1/storage/buckets/:bucketId') ); $audits - ->setParam('event', 'storage.buckets.update') - ->setParam('resource', 'storage/buckets/' . $bucket->getId()) - ->setParam('data', $bucket->getArrayCopy()) + ->setResource('storage/buckets/' . $bucket->getId()) + ->setPayload($bucket->getArrayCopy()) + ; + + $events + ->setParam('bucketId', $bucket->getId()) ; $usage->setParam('storage.buckets.update', 1); @@ -282,7 +292,7 @@ App::delete('/v1/storage/buckets/:bucketId') ->desc('Delete Bucket') ->groups(['api', 'storage']) ->label('scope', 'buckets.write') - ->label('event', 'storage.buckets.delete') + ->label('event', 'buckets.[bucketId].delete') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'storage') ->label('sdk.method', 'deleteBucket') @@ -299,7 +309,7 @@ App::delete('/v1/storage/buckets/:bucketId') ->action(function ($bucketId, $response, $dbForProject, $audits, $deletes, $events, $usage) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Event\Event $deletes */ /** @var Appwrite\Event\Event $events */ /** @var Appwrite\Stats\Stats $usage */ @@ -315,18 +325,21 @@ App::delete('/v1/storage/buckets/:bucketId') } $deletes - ->setParam('type', DELETE_TYPE_DOCUMENT) - ->setParam('document', $bucket) + ->setParam('bucketId', $bucket->getId()) + ->setPayload([ + 'type' => DELETE_TYPE_DOCUMENT, + 'document' => $bucket + ]) ; $events - ->setParam('eventData', $response->output($bucket, Response::MODEL_BUCKET)) + ->setParam('bucketId', $bucket->getId()) + ->setPayload($response->output($bucket, Response::MODEL_BUCKET)) ; $audits - ->setParam('event', 'storage.buckets.delete') - ->setParam('resource', 'storage/buckets/' . $bucket->getId()) - ->setParam('data', $bucket->getArrayCopy()) + ->setResource('storage/buckets/' . $bucket->getId()) + ->setPayload($bucket->getArrayCopy()) ; $usage->setParam('storage.buckets.delete', 1); @@ -339,7 +352,7 @@ App::post('/v1/storage/buckets/:bucketId/files') ->desc('Create File') ->groups(['api', 'storage']) ->label('scope', 'files.write') - ->label('event', 'storage.files.create') + ->label('event', 'buckets.[bucketId].files.[fileId].create') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'storage') ->label('sdk.method', 'createFile') @@ -369,7 +382,7 @@ App::post('/v1/storage/buckets/:bucketId/files') /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ /** @var Utopia\Database\Document $user */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Event\Event $events */ /** @var Appwrite\Stats\Stats $usage */ /** @var Utopia\Storage\Device $deviceFiles */ @@ -581,9 +594,7 @@ App::post('/v1/storage/buckets/:bucketId/files') 'metadata' => $metadata, ]); if ($permissionBucket) { - $file = Authorization::skip(function () use ($dbForProject, $bucket, $doc) { - return $dbForProject->createDocument('bucket_' . $bucket->getInternalId(), $doc); - }); + $file = Authorization::skip(fn () => $dbForProject->createDocument('bucket_' . $bucket->getInternalId(), $doc)); } else { $file = $dbForProject->createDocument('bucket_' . $bucket->getInternalId(), $doc); } @@ -603,9 +614,7 @@ App::post('/v1/storage/buckets/:bucketId/files') ->setAttribute('chunksUploaded', $chunksUploaded); if ($permissionBucket) { - $file = Authorization::skip(function () use ($dbForProject, $bucket, $fileId, $file) { - return $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file); - }); + $file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file)); } else { $file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file); } @@ -618,8 +627,7 @@ App::post('/v1/storage/buckets/:bucketId/files') } $audits - ->setParam('event', 'storage.files.create') - ->setParam('resource', 'storage/files/' . $file->getId()) + ->setResource('storage/files/' . $file->getId()) ; $usage @@ -651,9 +659,7 @@ App::post('/v1/storage/buckets/:bucketId/files') 'metadata' => $metadata, ]); if ($permissionBucket) { - $file = Authorization::skip(function () use ($dbForProject, $bucket, $doc) { - return $dbForProject->createDocument('bucket_' . $bucket->getInternalId(), $doc); - }); + $file = Authorization::skip(fn () => $dbForProject->createDocument('bucket_' . $bucket->getInternalId(), $doc)); } else { $file = $dbForProject->createDocument('bucket_' . $bucket->getInternalId(), $doc); } @@ -663,9 +669,7 @@ App::post('/v1/storage/buckets/:bucketId/files') ->setAttribute('metadata', $metadata); if ($permissionBucket) { - $file = Authorization::skip(function () use ($dbForProject, $bucket, $fileId, $file) { - return $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file); - }); + $file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file)); } else { $file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file); } @@ -677,13 +681,16 @@ App::post('/v1/storage/buckets/:bucketId/files') } } - $events->setParam('bucket', $bucket->getArrayCopy()); + $events + ->setParam('bucketId', $bucket->getId()) + ->setParam('fileId', $file->getId()) + ->setTrigger($bucket) + ; $metadata = null; // was causing leaks as it was passed by reference $response->setStatusCode(Response::STATUS_CODE_CREATED); $response->dynamic($file, Response::MODEL_FILE); - ; }); App::get('/v1/storage/buckets/:bucketId/files') @@ -1323,7 +1330,7 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId') ->desc('Update File') ->groups(['api', 'storage']) ->label('scope', 'files.write') - ->label('event', 'storage.files.update') + ->label('event', 'buckets.[bucketId].files.[fileId].update') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'storage') ->label('sdk.method', 'updateFile') @@ -1346,7 +1353,7 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId') /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ /** @var Utopia\Database\Document $user */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Stats\Stats $usage */ /** @var Appwrite\Event\Event $events */ /** @var string $mode */ @@ -1405,13 +1412,14 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId') $file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file); } - $events->setParam('bucket', $bucket->getArrayCopy()); - - $audits - ->setParam('event', 'storage.files.update') - ->setParam('resource', 'file/' . $file->getId()) + $events + ->setParam('bucketId', $bucket->getId()) + ->setParam('fileId', $file->getId()) + ->setTrigger($bucket) ; + $audits->setResource('file/' . $file->getId()); + $usage ->setParam('storage.files.update', 1) ->setParam('bucketId', $bucketId) @@ -1425,7 +1433,7 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId') ->desc('Delete File') ->groups(['api', 'storage']) ->label('scope', 'files.write') - ->label('event', 'storage.files.delete') + ->label('event', 'buckets.[bucketId].files.[fileId].delete') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'storage') ->label('sdk.method', 'deleteFile') @@ -1447,7 +1455,7 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId') /** @var Utopia\Database\Database $dbForProject */ /** @var Utopia\Database\Database $dbForProject */ /** @var Appwrite\Event\Event $events */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Stats\Stats $usage */ /** @var Utopia\Storage\Device $deviceFiles */ /** @var string $mode */ @@ -1505,10 +1513,7 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId') throw new Exception('Failed to delete file from device', 500, Exception::GENERAL_SERVER_ERROR); } - $audits - ->setParam('event', 'storage.files.delete') - ->setParam('resource', 'file/' . $file->getId()) - ; + $audits->setResource('file/' . $file->getId()); $usage ->setParam('storage', $file->getAttribute('size', 0) * -1) @@ -1517,8 +1522,10 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId') ; $events - ->setParam('eventData', $response->output($file, Response::MODEL_FILE)) - ->setParam('bucket', $bucket->getArrayCopy()) + ->setParam('bucketId', $bucket->getId()) + ->setParam('fileId', $file->getId()) + ->setTrigger($bucket) + ->setPayload($response->output($file, Response::MODEL_FILE)) ; $response->noContent(); diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 22705d3bf8..efbd2c3e50 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -26,7 +26,7 @@ use Utopia\Validator\WhiteList; App::post('/v1/teams') ->desc('Create Team') ->groups(['api', 'teams']) - ->label('event', 'teams.create') + ->label('event', 'teams.[teamId].create') ->label('scope', 'teams.write') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'teams') @@ -85,6 +85,8 @@ App::post('/v1/teams') $user = $dbForProject->updateDocument('users', $user->getId(), $user); } + $events->setParam('teamId', $team->getId()); + if (!empty($user->getId())) { $events->setParam('userId', $user->getId()); } @@ -169,7 +171,7 @@ App::get('/v1/teams/:teamId') App::put('/v1/teams/:teamId') ->desc('Update Team') ->groups(['api', 'teams']) - ->label('event', 'teams.update') + ->label('event', 'teams.[teamId].update') ->label('scope', 'teams.write') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'teams') @@ -182,9 +184,11 @@ App::put('/v1/teams/:teamId') ->param('name', null, new Text(128), 'New team name. Max length: 128 chars.') ->inject('response') ->inject('dbForProject') - ->action(function ($teamId, $name, $response, $dbForProject) { + ->inject('events') + ->action(function ($teamId, $name, $response, $dbForProject, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ + /** @var Appwrite\Event\Event $events */ $team = $dbForProject->getDocument('teams', $teamId); @@ -197,13 +201,15 @@ App::put('/v1/teams/:teamId') ->setAttribute('search', implode(' ', [$teamId, $name])) ); + $events->setParam('teamId', $team->getId()); + $response->dynamic($team, Response::MODEL_TEAM); }); App::delete('/v1/teams/:teamId') ->desc('Delete Team') ->groups(['api', 'teams']) - ->label('event', 'teams.delete') + ->label('event', 'teams.[teamId].delete') ->label('scope', 'teams.write') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'teams') @@ -244,12 +250,17 @@ App::delete('/v1/teams/:teamId') } $deletes - ->setParam('type', DELETE_TYPE_DOCUMENT) - ->setParam('document', $team) + ->setParam('teamId', $team->getId()) + ->setPayload([ + 'type' => DELETE_TYPE_DOCUMENT, + 'document' => $team + ]) + ; $events - ->setParam('eventData', $response->output($team, Response::MODEL_TEAM)) + ->setParam('teamId', $team->getId()) + ->setPayload($response->output($team, Response::MODEL_TEAM)) ; $response->noContent(); @@ -258,7 +269,7 @@ App::delete('/v1/teams/:teamId') App::post('/v1/teams/:teamId/memberships') ->desc('Create Team Membership') ->groups(['api', 'teams', 'auth']) - ->label('event', 'teams.memberships.create') + ->label('event', 'teams.[teamId].memberships.[membershipId].create') ->label('scope', 'teams.write') ->label('auth.type', 'invites') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) @@ -281,13 +292,15 @@ App::post('/v1/teams/:teamId/memberships') ->inject('locale') ->inject('audits') ->inject('mails') - ->action(function ($teamId, $email, $roles, $url, $name, $response, $project, $user, $dbForProject, $locale, $audits, $mails) { + ->inject('events') + ->action(function ($teamId, $email, $roles, $url, $name, $response, $project, $user, $dbForProject, $locale, $audits, $mails, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Document $project */ /** @var Utopia\Database\Document $user */ /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Event\Event $audits */ - /** @var Appwrite\Event\Event $mails */ + /** @var Appwrite\Event\Audit $audits */ + /** @var Appwrite\Event\Mail $mails */ + /** @var Appwrite\Event\Event $events */ if(empty(App::getEnv('_APP_SMTP_HOST'))) { throw new Exception('SMTP Disabled', 503, Exception::GENERAL_SMTP_DISABLED); @@ -398,24 +411,24 @@ App::post('/v1/teams/:teamId/memberships') if (!$isPrivilegedUser && !$isAppUser) { // No need of confirmation when in admin or app mode $mails - ->setParam('event', 'teams.memberships.create') - ->setParam('from', $project->getId()) - ->setParam('recipient', $email) - ->setParam('name', $name) - ->setParam('url', $url) - ->setParam('locale', $locale->default) - ->setParam('project', $project->getAttribute('name', ['[APP-NAME]'])) - ->setParam('owner', $user->getAttribute('name', '')) - ->setParam('team', $team->getAttribute('name', '[TEAM-NAME]')) - ->setParam('type', MAIL_TYPE_INVITATION) + ->setType(MAIL_TYPE_INVITATION) + ->setRecipient($email) + ->setUrl($url) + ->setName($name) + ->setLocale($locale->default) + ->setTeam($team) + ->setUser($user) ->trigger() ; } $audits - ->setParam('userId', $invitee->getId()) - ->setParam('event', 'teams.memberships.create') - ->setParam('resource', 'team/'.$teamId) + ->setResource('team/'.$teamId) + ; + + $events + ->setParam('teamId', $team->getId()) + ->setParam('membershipId', $membership->getId()) ; $response->setStatusCode(Response::STATUS_CODE_CREATED); @@ -548,7 +561,7 @@ App::get('/v1/teams/:teamId/memberships/:membershipId') App::patch('/v1/teams/:teamId/memberships/:membershipId') ->desc('Update Membership Roles') ->groups(['api', 'teams']) - ->label('event', 'teams.memberships.update') + ->label('event', 'teams.[teamId].memberships.[membershipId].update') ->label('scope', 'teams.write') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'teams') @@ -565,12 +578,14 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId') ->inject('user') ->inject('dbForProject') ->inject('audits') - ->action(function ($teamId, $membershipId, $roles, $request, $response, $user, $dbForProject, $audits) { + ->inject('events') + ->action(function ($teamId, $membershipId, $roles, $request, $response, $user, $dbForProject, $audits, $events) { /** @var Appwrite\Utopia\Request $request */ /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Document $user */ /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ + /** @var Appwrite\Event\Event $events */ $team = $dbForProject->getDocument('teams', $teamId); if ($team->isEmpty()) { @@ -612,10 +627,11 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId') Authorization::skip(fn () => $dbForProject->updateDocument('users', $profile->getId(), $profile)); - $audits - ->setParam('userId', $user->getId()) - ->setParam('event', 'teams.memberships.update') - ->setParam('resource', 'team/' . $teamId); + $audits->setResource('team/' . $teamId); + + $events + ->setParam('teamId', $team->getId()) + ->setParam('membershipId', $membership->getId()); $response->dynamic( $membership @@ -628,7 +644,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId') App::patch('/v1/teams/:teamId/memberships/:membershipId/status') ->desc('Update Team Membership Status') ->groups(['api', 'teams']) - ->label('event', 'teams.memberships.update.status') + ->label('event', 'teams.[teamId].memberships.[membershipId].update.status') ->label('scope', 'public') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'teams') @@ -647,13 +663,15 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') ->inject('dbForProject') ->inject('geodb') ->inject('audits') - ->action(function ($teamId, $membershipId, $userId, $secret, $request, $response, $user, $dbForProject, $geodb, $audits) { + ->inject('events') + ->action(function ($teamId, $membershipId, $userId, $secret, $request, $response, $user, $dbForProject, $geodb, $audits, $events) { /** @var Appwrite\Utopia\Request $request */ /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Document $user */ /** @var Utopia\Database\Database $dbForProject */ /** @var MaxMind\Db\Reader $geodb */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ + /** @var Appwrite\Event\Event $events */ $protocol = $request->getProtocol(); @@ -677,7 +695,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') throw new Exception('Secret key not valid', 401, Exception::TEAM_INVALID_SECRET); } - if ($userId != $membership->getAttribute('userId')) { + if ($userId !== $membership->getAttribute('userId')) { throw new Exception('Invite does not belong to current user ('.$user->getAttribute('email').')', 401, Exception::TEAM_INVITE_MISMATCH); } @@ -737,10 +755,11 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') $team = Authorization::skip(fn() => $dbForProject->updateDocument('teams', $team->getId(), $team->setAttribute('total', $team->getAttribute('total', 0) + 1))); - $audits - ->setParam('userId', $user->getId()) - ->setParam('event', 'teams.memberships.update.status') - ->setParam('resource', 'team/'.$teamId) + $audits->setResource('team/'.$teamId); + + $events + ->setParam('teamId', $team->getId()) + ->setParam('membershipId', $membership->getId()) ; if (!Config::getParam('domainVerification')) { @@ -763,7 +782,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') App::delete('/v1/teams/:teamId/memberships/:membershipId') ->desc('Delete Team Membership') ->groups(['api', 'teams']) - ->label('event', 'teams.memberships.delete') + ->label('event', 'teams.[teamId].memberships.[membershipId].delete') ->label('scope', 'teams.write') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'teams') @@ -780,7 +799,7 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId') ->action(function ($teamId, $membershipId, $response, $dbForProject, $audits, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Event\Event $events */ $membership = $dbForProject->getDocument('memberships', $membershipId); @@ -833,14 +852,12 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId') Authorization::skip(fn() => $dbForProject->updateDocument('teams', $team->getId(), $team)); } - $audits - ->setParam('userId', $membership->getAttribute('userId')) - ->setParam('event', 'teams.memberships.delete') - ->setParam('resource', 'team/'.$teamId) - ; + $audits->setResource('team/'.$teamId); $events - ->setParam('eventData', $response->output($membership, Response::MODEL_MEMBERSHIP)) + ->setParam('teamId', $team->getId()) + ->setParam('membershipId', $membership->getId()) + ->setPayload($response->output($membership, Response::MODEL_MEMBERSHIP)) ; $response->noContent(); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 3237e8d6ad..32d56f2ee1 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -466,7 +466,7 @@ App::patch('/v1/users/:userId/name') ->action(function ($userId, $name, $response, $dbForProject, $audits, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Event\Event $events */ $user = $dbForProject->getDocument('users', $userId); @@ -478,9 +478,7 @@ App::patch('/v1/users/:userId/name') $user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('name', $name)); $audits - ->setPayload(array_merge($audits->getPayload(), [ - 'resource' => 'user/'.$user->getId() - ])) + ->setResource('user/'.$user->getId()) ; $events @@ -511,7 +509,7 @@ App::patch('/v1/users/:userId/password') ->action(function ($userId, $password, $response, $dbForProject, $audits, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Event\Event $events */ $user = $dbForProject->getDocument('users', $userId); @@ -527,9 +525,7 @@ App::patch('/v1/users/:userId/password') $user = $dbForProject->updateDocument('users', $user->getId(), $user); $audits - ->setPayload(array_merge($audits->getPayload(), [ - 'resource' => 'user/'.$user->getId() - ])) + ->setResource('user/'.$user->getId()) ; $events @@ -560,7 +556,7 @@ App::patch('/v1/users/:userId/email') ->action(function ($userId, $email, $response, $dbForProject, $audits, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Event\Event $events */ $user = $dbForProject->getDocument('users', $userId); @@ -584,9 +580,7 @@ App::patch('/v1/users/:userId/email') $audits - ->setPayload(array_merge($audits->getPayload(), [ - 'resource' => 'user/'.$user->getId() - ])) + ->setResource('user/'.$user->getId()) ; $events @@ -690,6 +684,7 @@ App::delete('/v1/users/:userId/sessions/:sessionId') $events ->setParam('userId', $user->getId()) + ->setParam('sessionId', $sessionId) ; $response->noContent(); @@ -794,8 +789,11 @@ App::delete('/v1/users/:userId') $dbForProject->updateDocument('users', $userId, $user); $deletes - ->setParam('type', DELETE_TYPE_DOCUMENT) - ->setParam('document', $clone) + ->setParam('userId', $userId) + ->setPayload([ + 'type' => DELETE_TYPE_DOCUMENT, + 'document' => $clone + ]) ; $events diff --git a/app/controllers/general.php b/app/controllers/general.php index a65e07c81c..334cdb237d 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -12,6 +12,7 @@ use Appwrite\Extend\Exception; use Utopia\Config\Config; use Utopia\Domains\Domain; use Appwrite\Auth\Auth; +use Appwrite\Event\Certificate; use Appwrite\Network\Validator\Origin; use Appwrite\Utopia\Response\Filters\V11 as ResponseV11; use Appwrite\Utopia\Response\Filters\V12 as ResponseV12; @@ -90,12 +91,10 @@ App::init(function ($utopia, $request, $response, $console, $project, $dbForCons Console::info('Issuing a TLS certificate for the master domain (' . $domain->get() . ') in a few seconds...'); - Resque::enqueue('v1-certificates', 'CertificatesV1', [ - 'document' => $domainDocument, - 'domain' => $domain->get(), - 'validateTarget' => false, - 'validateCNAME' => false, - ]); + $event = new Certificate(); + $event + ->setDomain($domainDocument) + ->trigger(); } $domains[$domain->get()] = true; diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index f23d030a61..bec53e6f98 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -7,14 +7,16 @@ use Utopia\App; use Appwrite\Extend\Exception; use Utopia\Abuse\Abuse; use Utopia\Abuse\Adapters\TimeLimit; +use Utopia\Database\Database; use Utopia\Database\Document; +use Utopia\Database\Query; use Utopia\Storage\Device\DOSpaces; use Utopia\Database\Validator\Authorization; use Utopia\Storage\Device\Local; use Utopia\Storage\Device\S3; use Utopia\Storage\Storage; -App::init(function ($utopia, $request, $response, $project, $user, $events, $audits, $usage, $deletes, $database, $dbForProject, $mode) { +App::init(function ($utopia, $request, $response, $project, $user, $events, $audits, $mails, $usage, $deletes, $database, $dbForProject, $mode) { /** @var Utopia\App $utopia */ /** @var Appwrite\Utopia\Request $request */ /** @var Appwrite\Utopia\Response $response */ @@ -22,7 +24,8 @@ App::init(function ($utopia, $request, $response, $project, $user, $events, $aud /** @var Utopia\Database\Document $user */ /** @var Utopia\Registry\Registry $register */ /** @var Appwrite\Event\Event $events */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ + /** @var Appwrite\Event\Mail $mails */ /** @var Appwrite\Stats\Stats $usage */ /** @var Appwrite\Event\Event $deletes */ /** @var Appwrite\Event\Event $database */ @@ -94,28 +97,18 @@ App::init(function ($utopia, $request, $response, $project, $user, $events, $aud ->setUser($user) ; - $events - ->setParam('projectId', $project->getId()) - ->setParam('webhooks', $project->getAttribute('webhooks', [])) - ->setParam('userId', $user->getId()) - ->setParam('event', $route->getLabel('event', '')) - ->setParam('eventData', []) - ->setParam('functionId', null) - ->setParam('executionId', null) - ->setParam('trigger', 'event') + $mails + ->setProject($project) + ->setUser($user) ; $audits + ->setMode($mode) + ->setUserAgent($request->getUserAgent('')) + ->setIP($request->getIP()) ->setEvent($route->getLabel('event', '')) ->setProject($project) ->setUser($user) - ->setPayload([ - 'mode' => $mode, - 'userAgent' => $request->getUserAgent(''), - 'ip' => $request->getIP(), - 'data' => [], - 'resource' => '' - ]) ; $usage @@ -128,15 +121,10 @@ App::init(function ($utopia, $request, $response, $project, $user, $events, $aud ->setParam('networkResponseSize', 0) ->setParam('storage', 0) ; - - $deletes - ->setParam('projectId', $project->getId()) - ; - $database - ->setParam('projectId', $project->getId()) - ; -}, ['utopia', 'request', 'response', 'project', 'user', 'events', 'audits', 'usage', 'deletes', 'database', 'dbForProject', 'mode'], 'api'); + $deletes->setProject($project); + $database->setProject($project); +}, ['utopia', 'request', 'response', 'project', 'user', 'events', 'audits', 'mails', 'usage', 'deletes', 'database', 'dbForProject', 'mode'], 'api'); App::init(function ($utopia, $request, $project) { /** @var Utopia\App $utopia */ @@ -191,56 +179,53 @@ App::init(function ($utopia, $request, $project) { }, ['utopia', 'request', 'project'], 'auth'); -App::shutdown(function ($utopia, $request, $response, $project, $events, $audits, $usage, $deletes, $database, $mode) { +App::shutdown(function ($utopia, $request, $response, $project, $events, $audits, $usage, $deletes, $database, $mode, $dbForProject) { /** @var Utopia\App $utopia */ /** @var Appwrite\Utopia\Request $request */ /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Document $project */ /** @var Appwrite\Event\Event $events */ - /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Stats\Stats $usage */ /** @var Appwrite\Event\Event $deletes */ - /** @var Appwrite\Event\Event $database */ + /** @var Appwrite\Event\Database $database */ /** @var bool $mode */ + /** @var Utopia\Database\Database $dbForProject */ if (!empty($events->getEvent())) { - $allEvents = Event::generateEvents($events->getEvent(), $events->getParams()); - var_dump($request->getRoute()->getPath(), $events->getEvent(), $allEvents); - foreach ($project->getAttribute('webhooks', []) as $webhook) { - /** - * @var Document $webhook - */ - if (array_intersect($webhook->getAttribute('events', []), $allEvents)) { - $events - ->setClass(Event::WEBHOOK_CLASS_NAME) - ->setQueue(Event::WEBHOOK_QUEUE_NAME) - ->setTrigger($webhook) - ->setPayload($response->getPayload()) - ->trigger(); - } - } - } - if (!empty($events->getParam('event'))) { - if (empty($events->getParam('eventData'))) { - $events->setParam('eventData', $response->getPayload()); - } - - $functions = clone $events; - - $functions - ->setQueue('v1-functions') - ->setClass('FunctionsV1') + /** + * Trigger functions. + */ + $events + ->setClass(Event::FUNCTIONS_CLASS_NAME) + ->setQueue(Event::FUNCTIONS_QUEUE_NAME) + ->setPayload($response->getPayload()) ->trigger(); + /** + * Trigger webhooks. + */ + $events + ->setClass(Event::WEBHOOK_CLASS_NAME) + ->setQueue(Event::WEBHOOK_QUEUE_NAME) + ->setPayload($response->getPayload()) + ->trigger(); + + /** + * Trigger realtime. + */ if ($project->getId() !== 'console') { + $allEvents = Event::generateEvents($events->getEvent(), $events->getParams()); $payload = new Document($response->getPayload()); - $collection = new Document($events->getParam('collection') ?? []); - $bucket = new Document($events->getParam('bucket') ?? []); + $trigger = $events->getTrigger() ?? false; + + $collection = ($trigger && $trigger->getCollection() === 'collections') ? $trigger : null; + $bucket = ($trigger && $trigger->getCollection() === 'buckets') ? $trigger : null; $target = Realtime::fromPayload( - event: $events->getParam('event'), - payload: $payload, - project: $project, + event: $allEvents[0], + payload: $payload, + project: $project, collection: $collection, bucket: $bucket, ); @@ -248,7 +233,7 @@ App::shutdown(function ($utopia, $request, $response, $project, $events, $audits Realtime::send( $target['projectId'] ?? $project->getId(), $response->getPayload(), - $events->getParam('event'), + $allEvents[0], $target['channels'], $target['roles'], [ @@ -259,15 +244,18 @@ App::shutdown(function ($utopia, $request, $response, $project, $events, $audits } } - if (!empty($audits->getEvent())) { + if (!empty($audits->getResource())) { + foreach ($events->getParams() as $key => $value) { + $audits->setParam($key, $value); + } $audits->trigger(); } - if (!empty($deletes->getParam('type')) && !empty($deletes->getParam('document'))) { + if (!empty($deletes->getPayload())) { $deletes->trigger(); } - if (!empty($database->getParam('type')) && !empty($database->getParam('document'))) { + if (!empty($database->getType())) { $database->trigger(); } @@ -277,11 +265,11 @@ App::shutdown(function ($utopia, $request, $response, $project, $events, $audits && $mode !== APP_MODE_ADMIN // TODO: add check to make sure user is admin && !empty($route->getLabel('sdk.namespace', null))) { // Don't calculate console usage on admin mode - $usage - ->setParam('networkRequestSize', $request->getSize() + $usage->getParam('storage')) - ->setParam('networkResponseSize', $response->getSize()) - ->submit() - ; + // $usage + // ->setParam('networkRequestSize', $request->getSize() + $usage->getParam('storage')) + // ->setParam('networkResponseSize', $response->getSize()) + // ->submit() + // ; } -}, ['utopia', 'request', 'response', 'project', 'events', 'audits', 'usage', 'deletes', 'database', 'mode'], 'api'); \ No newline at end of file +}, ['utopia', 'request', 'response', 'project', 'events', 'audits', 'usage', 'deletes', 'database', 'mode', 'dbForProject'], 'api'); \ No newline at end of file diff --git a/app/init.php b/app/init.php index fd509297f6..224935016e 100644 --- a/app/init.php +++ b/app/init.php @@ -22,7 +22,11 @@ use Ahc\Jwt\JWT; use Ahc\Jwt\JWTException; use Appwrite\Extend\Exception; use Appwrite\Auth\Auth; +use Appwrite\Event\Audit; +use Appwrite\Event\Database as EventDatabase; +use Appwrite\Event\Delete; use Appwrite\Event\Event; +use Appwrite\Event\Mail; use Appwrite\Network\Validator\Email; use Appwrite\Network\Validator\IP; use Appwrite\Network\Validator\URL; @@ -604,10 +608,10 @@ App::setResource('locale', fn() => new Locale(App::getEnv('_APP_LOCALE', 'en'))) // Queues App::setResource('events', fn() => new Event('', '')); -App::setResource('audits', fn() => new Event(Event::AUDITS_QUEUE_NAME, Event::AUDITS_CLASS_NAME)); -App::setResource('mails', fn() => new Event(Event::MAILS_QUEUE_NAME, Event::MAILS_CLASS_NAME)); -App::setResource('deletes', fn() => new Event(Event::DELETE_QUEUE_NAME, Event::DELETE_CLASS_NAME)); -App::setResource('database', fn() => new Event(Event::DATABASE_QUEUE_NAME, Event::DATABASE_CLASS_NAME)); +App::setResource('audits', fn() => new Audit()); +App::setResource('mails', fn() => new Mail()); +App::setResource('deletes', fn() => new Delete()); +App::setResource('database', fn() => new EventDatabase()); App::setResource('usage', function($register) { return new Stats($register->get('statsd')); }, ['register']); diff --git a/app/tasks/maintenance.php b/app/tasks/maintenance.php index a6f37ff71b..05a9607883 100644 --- a/app/tasks/maintenance.php +++ b/app/tasks/maintenance.php @@ -11,46 +11,56 @@ $cli ->desc('Schedules maintenance tasks and publishes them to resque') ->action(function () { Console::title('Maintenance V1'); - Console::success(APP_NAME.' maintenance process v1 has started'); + Console::success(APP_NAME . ' maintenance process v1 has started'); function notifyDeleteExecutionLogs(int $interval) { Resque::enqueue(Event::DELETE_QUEUE_NAME, Event::DELETE_CLASS_NAME, [ - 'type' => DELETE_TYPE_EXECUTIONS, - 'timestamp' => time() - $interval + 'payload' => [ + 'type' => DELETE_TYPE_EXECUTIONS, + 'timestamp' => time() - $interval + ] ]); } - function notifyDeleteAbuseLogs(int $interval) + function notifyDeleteAbuseLogs(int $interval) { Resque::enqueue(Event::DELETE_QUEUE_NAME, Event::DELETE_CLASS_NAME, [ - 'type' => DELETE_TYPE_ABUSE, - 'timestamp' => time() - $interval + 'payload' => [ + 'type' => DELETE_TYPE_ABUSE, + 'timestamp' => time() - $interval + ] ]); } - function notifyDeleteAuditLogs(int $interval) + function notifyDeleteAuditLogs(int $interval) { Resque::enqueue(Event::DELETE_QUEUE_NAME, Event::DELETE_CLASS_NAME, [ - 'type' => DELETE_TYPE_AUDIT, - 'timestamp' => time() - $interval + 'payload' => [ + 'type' => DELETE_TYPE_AUDIT, + 'timestamp' => time() - $interval + ] ]); } - function notifyDeleteUsageStats(int $interval30m, int $interval1d) + function notifyDeleteUsageStats(int $interval30m, int $interval1d) { Resque::enqueue(Event::DELETE_QUEUE_NAME, Event::DELETE_CLASS_NAME, [ - 'type' => DELETE_TYPE_USAGE, - 'timestamp1d' => time() - $interval1d, - 'timestamp30m' => time() - $interval30m, + 'payload' => [ + 'type' => DELETE_TYPE_USAGE, + 'timestamp1d' => time() - $interval1d, + 'timestamp30m' => time() - $interval30m, + ] ]); } - function notifyDeleteConnections() + function notifyDeleteConnections() { Resque::enqueue(Event::DELETE_QUEUE_NAME, Event::DELETE_CLASS_NAME, [ - 'type' => DELETE_TYPE_REALTIME, - 'timestamp' => time() - 60 + 'payload' => [ + 'type' => DELETE_TYPE_REALTIME, + 'timestamp' => time() - 60 + ] ]); } @@ -59,10 +69,10 @@ $cli $executionLogsRetention = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_EXECUTION', '1209600'); $auditLogRetention = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', '1209600'); $abuseLogsRetention = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', '86400'); - $usageStatsRetention30m = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_USAGE_30M', '129600');//36 hours + $usageStatsRetention30m = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_USAGE_30M', '129600'); //36 hours $usageStatsRetention1d = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_USAGE_1D', '8640000'); // 100 days - Console::loop(function() use ($interval, $executionLogsRetention, $abuseLogsRetention, $auditLogRetention, $usageStatsRetention30m, $usageStatsRetention1d) { + Console::loop(function () use ($interval, $executionLogsRetention, $abuseLogsRetention, $auditLogRetention, $usageStatsRetention30m, $usageStatsRetention1d) { $time = date('d-m-Y H:i:s', time()); Console::info("[{$time}] Notifying deletes workers every {$interval} seconds"); notifyDeleteExecutionLogs($executionLogsRetention); @@ -71,4 +81,4 @@ $cli notifyDeleteUsageStats($usageStatsRetention30m, $usageStatsRetention1d); notifyDeleteConnections(); }, $interval); - }); \ No newline at end of file + }); diff --git a/app/tasks/ssl.php b/app/tasks/ssl.php index 2c32324fa3..e032d564d2 100644 --- a/app/tasks/ssl.php +++ b/app/tasks/ssl.php @@ -2,8 +2,10 @@ global $cli; +use Appwrite\Event\Certificate; use Utopia\App; use Utopia\CLI\Console; +use Utopia\Database\Document; $cli ->task('ssl') @@ -11,13 +13,13 @@ $cli ->action(function () { $domain = App::getEnv('_APP_DOMAIN', ''); - Console::log('Issue a TLS certificate for master domain ('.$domain.') in 30 seconds. + Console::log('Issue a TLS certificate for master domain (' . $domain . ') in 30 seconds. Make sure your domain points to your server or restart to try again.'); - ResqueScheduler::enqueueAt(\time() + 30, 'v1-certificates', 'CertificatesV1', [ - 'document' => [], - 'domain' => $domain, - 'validateTarget' => false, - 'validateCNAME' => false, - ]); - }); \ No newline at end of file + $event = new Certificate(); + $event + ->setDomain(new Document([ + 'domain' => $domain + ])) + ->trigger(); + }); diff --git a/app/workers/audits.php b/app/workers/audits.php index 5819a31384..2a0a85f6f4 100644 --- a/app/workers/audits.php +++ b/app/workers/audits.php @@ -24,19 +24,19 @@ class AuditsV1 extends Worker public function run(): void { $events = $this->args['events']; + $payload = $this->args['payload']; + $mode = $this->args['mode']; + $resource = $this->args['resource']; + $userAgent = $this->args['userAgent']; + $ip = $this->args['ip']; + $user = new Document($this->args['user']); $project = new Document($this->args['project']); - $payload = $this->args['payload']; $userName = $user->getAttribute('name', ''); $userEmail = $user->getAttribute('email', ''); $event = $events[0]; - $mode = $payload['mode']; - $resource = $payload['resource']; - $userAgent = $payload['userAgent']; - $ip = $payload['ip']; - $data = $payload['data']; $dbForProject = $this->getProjectDB($project->getId()); $audit = new Audit($dbForProject); @@ -44,12 +44,11 @@ class AuditsV1 extends Worker 'userName' => $userName, 'userEmail' => $userEmail, 'mode' => $mode, - 'data' => $data, + 'data' => $payload, ]); } public function shutdown(): void { - // ... Remove environment for this job } } diff --git a/app/workers/builds.php b/app/workers/builds.php index a81ccd9112..91b93699b8 100644 --- a/app/workers/builds.php +++ b/app/workers/builds.php @@ -21,7 +21,7 @@ Console::success(APP_NAME.' build worker v1 has started'); // TODO: Executor should return appropriate response codes. class BuildsV1 extends Worker -{ +{ /** * @var Executor */ @@ -42,7 +42,7 @@ class BuildsV1 extends Worker $projectId = $this->args['projectId'] ?? ''; $functionId = $this->args['resourceId'] ?? ''; $deploymentId = $this->args['deploymentId'] ?? ''; - + switch ($type) { case BUILD_TYPE_DEPLOYMENT: case BUILD_TYPE_RETRY: @@ -61,7 +61,7 @@ class BuildsV1 extends Worker $dbForProject = $this->getProjectDB($projectId); $dbForConsole = $this->getConsoleDB(); $project = $dbForConsole->getDocument('projects', $projectId); - + $function = $dbForProject->getDocument('functions', $functionId); if ($function->isEmpty()) { throw new Exception('Function not found', 404); @@ -177,7 +177,7 @@ class BuildsV1 extends Worker $build = $dbForProject->updateDocument('builds', $buildId, $build); /** - * Send realtime Event + * Send realtime Event */ $target = Realtime::fromPayload('functions.deployments.update', $build, $project); Realtime::send( diff --git a/app/workers/certificates.php b/app/workers/certificates.php index 9f9ce33dbd..e6c64790b5 100644 --- a/app/workers/certificates.php +++ b/app/workers/certificates.php @@ -42,16 +42,12 @@ class CertificatesV1 extends Worker Authorization::disable(); - // Args - $document = $this->args['document']; - $domain = $this->args['domain']; - - // Validation Args + $document = new Document($this->args['domain'] ?? []); $validateTarget = $this->args['validateTarget'] ?? true; $validateCNAME = $this->args['validateCNAME'] ?? true; // Options - $domain = new Domain((!empty($domain)) ? $domain : ''); + $domain = new Domain($document->getAttribute('domain')); $expiry = 60 * 60 * 24 * 30 * 2; // 60 days $safety = 60 * 60; // 1 hour $renew = (\time() + $expiry); @@ -158,11 +154,10 @@ class CertificatesV1 extends Worker throw new Exception('Failed saving certificate to DB'); } - if(!empty($document)) { - $certificate = new Document(\array_merge($document, [ - 'updated' => \time(), - 'certificateId' => $certificate->getId(), - ])); + if($document->getId()) { + $document + ->setAttribute('updated', \time()) + ->setAttribute('certificateId', $certificate->getId()); $certificate = $dbForConsole->updateDocument('domains', $certificate->getId(), $certificate); diff --git a/app/workers/database.php b/app/workers/database.php index d95ce122d0..f9bdb04e4e 100644 --- a/app/workers/database.php +++ b/app/workers/database.php @@ -20,12 +20,11 @@ class DatabaseV1 extends Worker public function run(): void { Authorization::disable(); - $projectId = $this->args['projectId'] ?? ''; - $type = $this->args['type'] ?? ''; - $collection = $this->args['collection'] ?? []; - $collection = new Document($collection); - $document = $this->args['document'] ?? []; - $document = new Document($document); + + $type = $this->args['type']; + $project = new Document($this->args['project']); + $collection = new Document($this->args['collection'] ?? []); + $document = new Document($this->args['document'] ?? []); if($collection->isEmpty()) { throw new Exception('Missing collection'); @@ -37,16 +36,16 @@ class DatabaseV1 extends Worker switch (strval($type)) { case DATABASE_TYPE_CREATE_ATTRIBUTE: - $this->createAttribute($collection, $document, $projectId); + $this->createAttribute($collection, $document, $project->getId()); break; case DATABASE_TYPE_DELETE_ATTRIBUTE: - $this->deleteAttribute($collection, $document, $projectId); + $this->deleteAttribute($collection, $document, $project->getId()); break; case DATABASE_TYPE_CREATE_INDEX: - $this->createIndex($collection, $document, $projectId); + $this->createIndex($collection, $document, $project->getId()); break; case DATABASE_TYPE_DELETE_INDEX: - $this->deleteIndex($collection, $document, $projectId); + $this->deleteIndex($collection, $document, $project->getId()); break; default: diff --git a/app/workers/deletes.php b/app/workers/deletes.php index 963488a00a..9418d8b642 100644 --- a/app/workers/deletes.php +++ b/app/workers/deletes.php @@ -19,6 +19,7 @@ use Utopia\Audit\Audit; require_once __DIR__ . '/../init.php'; Authorization::disable(); +Authorization::setDefaultStatus(false); Console::title('Deletes V1 Worker'); Console::success(APP_NAME . ' deletes worker v1 has started' . "\n"); @@ -40,34 +41,36 @@ class DeletesV1 extends Worker public function run(): void { - $projectId = $this->args['projectId'] ?? ''; - $type = $this->args['type'] ?? ''; + $project = new Document($this->args['project'] ?? []); + $payload = $this->args['payload'] ?? []; + + $type = $payload['type'] ?? ''; switch (strval($type)) { case DELETE_TYPE_DOCUMENT: - $document = new Document($this->args['document'] ?? []); + $document = new Document($payload['document'] ?? []); switch ($document->getCollection()) { case DELETE_TYPE_COLLECTIONS: - $this->deleteCollection($document, $projectId); + $this->deleteCollection($document, $project->getId()); break; case DELETE_TYPE_PROJECTS: $this->deleteProject($document); break; case DELETE_TYPE_FUNCTIONS: - $this->deleteFunction($document, $projectId); + $this->deleteFunction($document, $project->getId()); break; case DELETE_TYPE_DEPLOYMENTS: - $this->deleteDeployment($document, $projectId); + $this->deleteDeployment($document, $project->getId()); break; case DELETE_TYPE_USERS: - $this->deleteUser($document, $projectId); + $this->deleteUser($document, $project->getId()); break; case DELETE_TYPE_TEAMS: - $this->deleteMemberships($document, $projectId); + $this->deleteMemberships($document, $project->getId()); break; case DELETE_TYPE_BUCKETS: - $this->deleteBucket($document, $projectId); + $this->deleteBucket($document, $project->getId()); break; default: Console::error('No lazy delete operation available for document of type: ' . $document->getCollection()); @@ -76,38 +79,38 @@ class DeletesV1 extends Worker break; case DELETE_TYPE_EXECUTIONS: - $this->deleteExecutionLogs($this->args['timestamp']); + $this->deleteExecutionLogs($payload['timestamp']); break; case DELETE_TYPE_AUDIT: - $timestamp = $this->args['timestamp'] ?? 0; - $document = new Document($this->args['document'] ?? []); + $timestamp = $payload['timestamp'] ?? 0; + $document = new Document($payload['document'] ?? []); if (!empty($timestamp)) { - $this->deleteAuditLogs($this->args['timestamp']); + $this->deleteAuditLogs($payload['timestamp']); } if (!$document->isEmpty()) { - $this->deleteAuditLogsByResource('document/' . $document->getId(), $projectId); + $this->deleteAuditLogsByResource('document/' . $document->getId(), $project->getId()); } break; case DELETE_TYPE_ABUSE: - $this->deleteAbuseLogs($this->args['timestamp']); + $this->deleteAbuseLogs($payload['timestamp']); break; case DELETE_TYPE_REALTIME: - $this->deleteRealtimeUsage($this->args['timestamp']); + $this->deleteRealtimeUsage($payload['timestamp']); break; case DELETE_TYPE_CERTIFICATES: - $document = new Document($this->args['document']); + $document = new Document($payload['document']); $this->deleteCertificates($document); break; case DELETE_TYPE_USAGE: - $this->deleteUsageStats($this->args['timestamp1d'], $this->args['timestamp30m']); + $this->deleteUsageStats($payload['timestamp1d'], $payload['timestamp30m']); break; default: Console::error('No delete operation for type: ' . $type); @@ -205,7 +208,7 @@ class DeletesV1 extends Worker * DO NOT DELETE THE USER RECORD ITSELF. * WE RETAIN THE USER RECORD TO RESERVE THE USER ID AND ENSURE THAT THE USER ID IS NOT REUSED. */ - + $userId = $document->getId(); $user = $this->getProjectDB($projectId)->getDocument('users', $userId); @@ -213,9 +216,10 @@ class DeletesV1 extends Worker $this->deleteByGroup('sessions', [ new Query('userId', Query::TYPE_EQUAL, [$userId]) ], $this->getProjectDB($projectId)); - + $user->setAttribute('sessions', []); - $updated = $this->getProjectDB($projectId)->updateDocument('users', $userId, $user); + + $this->getProjectDB($projectId)->updateDocument('users', $userId, $user); // Delete Memberships and decrement team membership counts $this->deleteByGroup('memberships', [ diff --git a/app/workers/functions.php b/app/workers/functions.php index 2d4cdb2e54..94f608d464 100644 --- a/app/workers/functions.php +++ b/app/workers/functions.php @@ -7,7 +7,6 @@ use Appwrite\Stats\Stats; use Appwrite\Utopia\Response\Model\Execution; use Cron\CronExpression; use Executor\Executor; -use Swoole\Runtime; use Utopia\App; use Utopia\CLI\Console; use Utopia\Config\Config; @@ -15,23 +14,19 @@ use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; -require_once __DIR__.'/../init.php'; +require_once __DIR__ . '/../init.php'; Console::title('Functions V1 Worker'); Console::success(APP_NAME . ' functions worker v1 has started'); class FunctionsV1 extends Worker { - /** - * @var Executor - */ - private $executor = null; - + private ?Executor $executor = null; public array $args = []; - public array $allowed = []; - public function getName(): string { + public function getName(): string + { return "functions"; } @@ -42,19 +37,22 @@ class FunctionsV1 extends Worker public function run(): void { - $projectId = $this->args['projectId'] ?? ''; - $functionId = $this->args['functionId'] ?? ''; - $webhooks = $this->args['webhooks'] ?? []; - $executionId = $this->args['executionId'] ?? ''; - $trigger = $this->args['trigger'] ?? ''; - $event = $this->args['event'] ?? ''; - $scheduleOriginal = $this->args['scheduleOriginal'] ?? ''; - $eventData = (!empty($this->args['eventData'])) ? json_encode($this->args['eventData']) : ''; - $data = $this->args['data'] ?? ''; - $userId = $this->args['userId'] ?? ''; - $jwt = $this->args['jwt'] ?? ''; + $events = $this->args['events']; + $payload = $this->args['payload']; + $user = new Document($this->args['user'] ?? []); + $project = new Document($this->args['project'] ?? []); + $execution = new Document($this->args['trigger'] ?? []); - $database = $this->getProjectDB($projectId); + $event = $events[0] ?? ''; + $data = $payload['data'] ?? ''; + $jwt = $payload['jwt'] ?? ''; + $webhooks = $project->getAttribute('webhooks', []); + $trigger = $execution->getAttribute('trigger', ''); + $scheduleOriginal = $execution->getAttribute('scheduleOriginal', ''); + $eventData = !empty($execution->getAttribute('eventData')) ? json_encode($execution->getAttribute('eventData')) : ''; + $jwt = $execution->getAttribute('jwt', ''); + + $database = $this->getProjectDB($project->getId()); switch ($trigger) { case 'event': @@ -72,25 +70,23 @@ class FunctionsV1 extends Worker Console::log('Fetched ' . $sum . ' functions...'); foreach ($functions as $function) { - $events = $function->getAttribute('events', []); - - if (!\in_array($event, $events)) { + if (!array_intersect($events, $function->getAttribute('events', []))) { continue; } Console::success('Iterating function: ' . $function->getAttribute('name')); $this->execute( - projectId: $projectId, + projectId: $project->getId(), function: $function, dbForProject: $database, - executionId: $executionId, + executionId: $execution->getId(), webhooks: $webhooks, trigger: $trigger, event: $event, eventData: $eventData, data: $data, - userId: $userId, + userId: $user->getId(), jwt: $jwt ); @@ -98,9 +94,29 @@ class FunctionsV1 extends Worker } } + break; + case 'http': + $function = Authorization::skip(fn() => $database->getDocument('functions', $execution->getAttribute('functionId'))); + + $this->execute( + projectId: $project->getId(), + function: $function, + dbForProject: $database, + executionId: $execution->getId(), + webhooks: $webhooks, + trigger: $trigger, + event: '', + eventData: '', + data: $data, + userId: $user->getId(), + jwt: $jwt + ); + break; case 'schedule': + $function = Authorization::skip(fn() => $database->getDocument('functions', $execution->getAttribute('functionId'))); + /* * 1. Get Original Task * 2. Check for updates @@ -115,10 +131,8 @@ class FunctionsV1 extends Worker */ // Reschedule - $function = Authorization::skip(fn() => $database->getDocument('functions', $functionId)); - if (empty($function->getId())) { - throw new Exception('Function not found ('.$functionId.')'); + throw new Exception('Function not found (' . $function->getId() . ')'); } if ($scheduleOriginal && $scheduleOriginal !== $function->getAttribute('schedule')) { // Schedule has changed from previous run, ignore this run. @@ -132,60 +146,39 @@ class FunctionsV1 extends Worker ->setAttribute('scheduleNext', $next) ->setAttribute('schedulePrevious', \time()); - $function = Authorization::skip(function() use ($database, $function, $next, $functionId) { - $function = $database->updateDocument('functions', $function->getId(), new Document(array_merge($function->getArrayCopy(), [ + $function = Authorization::skip(fn () => $database->updateDocument( + 'functions', + $function->getId(), + new Document(array_merge($function->getArrayCopy(), [ 'scheduleNext' => (int)$next, - ]))); - - if ($function === false) { - throw new Exception('Function update failed (' . $functionId . ')'); - } - return $function; - }); + ])) + )); + + if ($function === false) { + throw new Exception('Function update failed.'); + } ResqueScheduler::enqueueAt($next, Event::FUNCTIONS_QUEUE_NAME, Event::FUNCTIONS_CLASS_NAME, [ - 'projectId' => $projectId, + 'projectId' => $project->getId(), 'webhooks' => $webhooks, 'functionId' => $function->getId(), - 'userId' => $userId, + 'userId' => $user->getId(), 'executionId' => null, 'trigger' => 'schedule', 'scheduleOriginal' => $function->getAttribute('schedule', ''), ]); // Async task reschedule $this->execute( - projectId: $projectId, + projectId: $project->getId(), function: $function, dbForProject: $database, - executionId: $executionId, + executionId: $execution->getId(), webhooks: $webhooks, trigger: $trigger, event: $event, eventData: $eventData, data: $data, - userId: $userId, - jwt: $jwt - ); - break; - - case 'http': - $function = Authorization::skip(fn() => $database->getDocument('functions', $functionId)); - - if (empty($function->getId())) { - throw new Exception('Function not found ('.$functionId.')'); - } - - $this->execute( - projectId: $projectId, - function: $function, - dbForProject: $database, - executionId: $executionId, - webhooks: $webhooks, - trigger: $trigger, - event: $event, - eventData: $eventData, - data: $data, - userId: $userId, + userId: $user->getId(), jwt: $jwt ); @@ -200,7 +193,7 @@ class FunctionsV1 extends Worker string $executionId, array $webhooks, string $trigger, - string $event, + string $event, string $eventData, string $data, string $userId, @@ -211,7 +204,7 @@ class FunctionsV1 extends Worker $deploymentId = $function->getAttribute('deployment', ''); /** Check if deployment exists */ - $deployment = Authorization::skip(fn() => $dbForProject->getDocument('deployments', $deploymentId)); + $deployment = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $deploymentId)); if ($deployment->getAttribute('resourceId') !== $functionId) { throw new Exception('Deployment not found. Create deployment before trying to execute a function', 404); @@ -222,7 +215,7 @@ class FunctionsV1 extends Worker } /** Check if build has exists */ - $build = Authorization::skip(fn() => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''))); + $build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''))); if ($build->isEmpty()) { throw new Exception('Build not found', 404); } @@ -233,14 +226,15 @@ class FunctionsV1 extends Worker /** Check if runtime is supported */ $runtimes = Config::getParam('runtimes', []); - $runtime = (isset($runtimes[$function->getAttribute('runtime', '')])) ? $runtimes[$function->getAttribute('runtime', '')] : null; - if (\is_null($runtime)) { + if (!\array_key_exists($function->getAttribute('runtime'), $runtimes)) { throw new Exception('Runtime "' . $function->getAttribute('runtime', '') . '" is not supported', 400); } + $runtime = $runtimes[$function->getAttribute('runtime')]; + /** Create execution or update execution status */ - $execution = Authorization::skip(function() use ($dbForProject, &$executionId, $functionId, $deploymentId, $trigger, $userId) { + $execution = Authorization::skip(function () use ($dbForProject, &$executionId, $functionId, $deploymentId, $trigger, $userId) { $execution = $dbForProject->getDocument('executions', $executionId); if ($execution->isEmpty()) { $executionId = $dbForProject->getId(); @@ -259,13 +253,14 @@ class FunctionsV1 extends Worker 'time' => 0.0, 'search' => implode(' ', [$functionId, $executionId]), ])); - + if ($execution->isEmpty()) { throw new Exception('Failed to create or read execution'); } } $execution->setAttribute('status', 'processing'); $execution = $dbForProject->updateDocument('executions', $executionId, $execution); + return $execution; }); @@ -301,19 +296,21 @@ class FunctionsV1 extends Worker ); /** Update execution status */ - $execution->setAttribute('status', $executionResponse['status']); - $execution->setAttribute('statusCode', $executionResponse['statusCode']); - $execution->setAttribute('stdout', $executionResponse['stdout']); - $execution->setAttribute('stderr', $executionResponse['stderr']); - $execution->setAttribute('time', $executionResponse['time']); + $execution + ->setAttribute('status', $executionResponse['status']) + ->setAttribute('statusCode', $executionResponse['statusCode']) + ->setAttribute('stdout', $executionResponse['stdout']) + ->setAttribute('stderr', $executionResponse['stderr']) + ->setAttribute('time', $executionResponse['time']); } catch (\Throwable $th) { - $execution->setAttribute('status', 'failed'); - $execution->setAttribute('statusCode', $th->getCode()); - $execution->setAttribute('stderr', $th->getMessage()); + $execution + ->setAttribute('status', 'failed') + ->setAttribute('statusCode', $th->getCode()) + ->setAttribute('stderr', $th->getMessage()); Console::error($th->getMessage()); } - $execution = Authorization::skip(fn() => $dbForProject->updateDocument('executions', $executionId, $execution)); + $execution = Authorization::skip(fn () => $dbForProject->updateDocument('executions', $executionId, $execution)); /** Trigger Webhook */ $executionModel = new Execution(); @@ -323,8 +320,8 @@ class FunctionsV1 extends Worker ->setParam('userId', $userId) ->setParam('webhooks', $webhooks) ->setParam('event', 'functions.executions.update') - ->setParam('eventData', $execution->getArrayCopy(array_keys($executionModel->getRules()))); - $executionUpdate->trigger(); + ->setParam('eventData', $execution->getArrayCopy(array_keys($executionModel->getRules()))) + ->trigger(); /** Trigger realtime event */ $target = Realtime::fromPayload('functions.executions.update', $execution); @@ -357,7 +354,6 @@ class FunctionsV1 extends Worker ->setParam('networkRequestSize', 0) ->setParam('networkResponseSize', 0) ->submit(); - $usage->submit(); } } diff --git a/app/workers/mails.php b/app/workers/mails.php index dc924a8867..aaedf8aeed 100644 --- a/app/workers/mails.php +++ b/app/workers/mails.php @@ -31,28 +31,30 @@ class MailsV1 extends Worker return; } - $payload = $this->args['payload']; + $project = new Document($this->args['project']); + $user = new Document($this->args['user'] ?? []); + $team = new Document($this->args['team'] ?? []); - $recipient = $payload['recipient']; - $url = $payload['url']; - $project = $payload['project']; - $type = $payload['type']; - $name = $payload['name'] ?? $recipient; + $recipient = $this->args['recipient']; + $url = $this->args['url']; + $name = $this->args['name']; + $type = $this->args['type']; $prefix = $this->getPrefix($type); - $locale = new Locale($payload['locale']); + $locale = new Locale($this->args['locale']); + $projectName = $project->getAttribute('name', ['[APP-NAME]']); if (!$this->doesLocaleExist($locale, $prefix)) { $locale->setDefault('en'); } - $from = $payload['from'] === 'console' ? '' : \sprintf($locale->getText('emails.sender'), $project); + $from = $project->getId() === 'console' ? '' : \sprintf($locale->getText('emails.sender'), $projectName); $body = Template::fromFile(__DIR__ . '/../config/locale/templates/email-base.tpl'); $subject = ''; switch ($type) { case MAIL_TYPE_INVITATION: - $subject = \sprintf($locale->getText("$prefix.subject"), $payload['team'], $project); - $body->setParam('{{owner}}', $payload['owner']); - $body->setParam('{{team}}', $payload['team']); + $subject = \sprintf($locale->getText("$prefix.subject"), $team->getAttribute('name'), $projectName); + $body->setParam('{{owner}}', $user->getAttribute('name')); + $body->setParam('{{team}}', $team->getAttribute('name')); break; case MAIL_TYPE_RECOVERY: case MAIL_TYPE_VERIFICATION: @@ -72,7 +74,7 @@ class MailsV1 extends Worker ->setParam('{{footer}}', $locale->getText("$prefix.footer")) ->setParam('{{thanks}}', $locale->getText("$prefix.thanks")) ->setParam('{{signature}}', $locale->getText("$prefix.signature")) - ->setParam('{{project}}', $project) + ->setParam('{{project}}', $projectName) ->setParam('{{direction}}', $locale->getText('settings.direction')) ->setParam('{{bg-body}}', '#f7f7f7') ->setParam('{{bg-content}}', '#ffffff') diff --git a/app/workers/webhooks.php b/app/workers/webhooks.php index 571bbe7102..aa2e84fab7 100644 --- a/app/workers/webhooks.php +++ b/app/workers/webhooks.php @@ -12,6 +12,8 @@ Console::success(APP_NAME . ' webhooks worker v1 has started'); class WebhooksV1 extends Worker { + protected array $errors = []; + public function getName(): string { return "webhooks"; @@ -23,15 +25,24 @@ class WebhooksV1 extends Worker public function run(): void { - $errors = []; - - // Event $events = $this->args['events']; - $user = new Document($this->args['user']); + $payload = json_encode($this->args['payload']); $project = new Document($this->args['project']); - $webhook = new Document($this->args['trigger'] ?? []); - $payload = \json_encode($this->args['payload']); + $user = new Document($this->args['user'] ?? []); + foreach ($project->getAttribute('webhooks', []) as $webhook) { + if (array_intersect($webhook->getAttribute('events', []), $events)) { + $this->execute($events, $payload, $webhook, $user, $project); + } + } + + if (!empty($this->errors)) { + throw new Exception(\implode(" / \n\n", $this->errors)); + } + } + + protected function execute(array $events, string $payload, Document $webhook, Document $user, Document $project): void + { $httpUser = $webhook->getAttribute('httpUser'); $httpPass = $webhook->getAttribute('httpPass'); @@ -72,17 +83,14 @@ class WebhooksV1 extends Worker } if (false === \curl_exec($ch)) { - $errors[] = \curl_error($ch) . ' in events ' . implode(', ', $events) . ' for webhook ' . $webhook->getAttribute('name'); + $this->errors[] = \curl_error($ch) . ' in events ' . implode(', ', $events) . ' for webhook ' . $webhook->getAttribute('name'); } \curl_close($ch); - - if (!empty($errors)) { - throw new Exception(\implode(" / \n\n", $errors)); - } } public function shutdown(): void { + $this->errors = []; } } diff --git a/src/Appwrite/Event/Audit.php b/src/Appwrite/Event/Audit.php new file mode 100644 index 0000000000..cceaa41423 --- /dev/null +++ b/src/Appwrite/Event/Audit.php @@ -0,0 +1,81 @@ +resource = $resource; + + return $this; + } + + public function getResource(): string + { + return $this->resource; + } + + public function setMode(string $mode): self + { + $this->mode = $mode; + + return $this; + } + + public function getMode(): string + { + return $this->mode; + } + + public function setUserAgent(string $userAgent): self + { + $this->userAgent = $userAgent; + + return $this; + } + + public function getUserAgent(): string + { + return $this->userAgent; + } + + public function setIP(string $ip): self + { + $this->ip = $ip; + + return $this; + } + + public function getIP(): string + { + return $this->ip; + } + + public function trigger(): string|bool + { + return Resque::enqueue($this->queue, $this->class, [ + 'project' => $this->project, + 'user' => $this->user, + 'payload' => $this->payload, + 'trigger' => $this->trigger, + 'resource' => $this->resource, + 'mode' => $this->mode, + 'ip' => $this->ip, + 'userAgent' => $this->userAgent, + 'events' => Event::generateEvents($this->getEvent(), $this->getParams()) + ]); + } +} \ No newline at end of file diff --git a/src/Appwrite/Event/Certificate.php b/src/Appwrite/Event/Certificate.php new file mode 100644 index 0000000000..1055df697a --- /dev/null +++ b/src/Appwrite/Event/Certificate.php @@ -0,0 +1,64 @@ +domain = $domain; + + return $this; + } + + public function getDomain(): ?Document + { + return $this->domain; + } + + public function setValidateTarget(bool $validateTarget): self + { + $this->validateTarget = $validateTarget; + + return $this; + } + + public function getValidateTarget(): bool + { + return $this->validateTarget; + } + + public function setValidateCNAME(bool $validateCNAME): self + { + $this->validateCNAME = $validateCNAME; + + return $this; + } + + public function getValidateCNAME(): bool + { + return $this->validateCNAME; + } + + public function trigger(): string|bool + { + return Resque::enqueue($this->queue, $this->class, [ + 'project' => $this->project, + 'domain' => $this->domain, + 'validateTarget' => $this->validateTarget, + 'validateCNAME' => $this->validateCNAME + ]); + } +} \ No newline at end of file diff --git a/src/Appwrite/Event/Database.php b/src/Appwrite/Event/Database.php new file mode 100644 index 0000000000..71700dbf4b --- /dev/null +++ b/src/Appwrite/Event/Database.php @@ -0,0 +1,66 @@ +type = $type; + + return $this; + } + + public function getType(): string + { + return $this->type; + } + + public function setCollection(Document $collection): self + { + $this->collection = $collection; + + return $this; + } + + public function getCollection(): Document + { + return $this->collection; + } + + public function setDocument(Document $document): self + { + $this->document = $document; + + return $this; + } + + public function getDocument(): Document + { + return $this->document; + } + + public function trigger(): string|bool + { + return Resque::enqueue($this->queue, $this->class, [ + 'project' => $this->project, + 'user' => $this->user, + 'type' => $this->type, + 'collection' => $this->collection, + 'document' => $this->document, + 'events' => Event::generateEvents($this->getEvent(), $this->getParams()) + ]); + } +} \ No newline at end of file diff --git a/src/Appwrite/Event/Delete.php b/src/Appwrite/Event/Delete.php new file mode 100644 index 0000000000..7abd7ae2bb --- /dev/null +++ b/src/Appwrite/Event/Delete.php @@ -0,0 +1,50 @@ +type = $type; + + return $this; + } + + public function getType(): string + { + return $this->type; + } + + public function setDocument(Document $document): self + { + $this->document = $document; + + return $this; + } + + public function getDocument(): Document + { + return $this->document; + } + + public function trigger(): string|bool + { + return Resque::enqueue($this->queue, $this->class, [ + 'project' => $this->project, + 'type' => $this->type, + 'document' => $this->document, + ]); + } +} \ No newline at end of file diff --git a/src/Appwrite/Event/Event.php b/src/Appwrite/Event/Event.php index 22a71b946a..c6c69b111c 100644 --- a/src/Appwrite/Event/Event.php +++ b/src/Appwrite/Event/Event.php @@ -103,26 +103,26 @@ class Event public function setProject(Document $project): self { - $this->projectId = $project; + $this->project = $project; return $this; } - public function getProjectId(): Document + public function getProject(): Document { - return $this->projectId; + return $this->project; } public function setUser(Document $user): self { - $this->userId = $user; + $this->user = $user; return $this; } - public function getUserId(): Document + public function getUser(): Document { - return $this->userId; + return $this->user; } public function setPayload(array $payload): self @@ -144,7 +144,7 @@ class Event return $this; } - public function getTrigger(): Document + public function getTrigger(): ?Document { return $this->trigger; } @@ -215,8 +215,8 @@ class Event public function trigger(): string|bool { return Resque::enqueue($this->queue, $this->class, [ - 'project' => $this->projectId, - 'user' => $this->userId, + 'project' => $this->project, + 'user' => $this->user, 'payload' => $this->payload, 'trigger' => $this->trigger, 'events' => Event::generateEvents($this->getEvent(), $this->getParams()) @@ -294,11 +294,11 @@ class Event $action = $parsed['action']; $attribute = $parsed['attribute']; - if ($resource && !\in_array(\trim($resource, '[]'), $paramKeys)) { + if ($resource && !\in_array(\trim($resource, "\[\]"), $paramKeys)) { throw new InvalidArgumentException("{$resource} is missing from the params."); } - if ($subResource && !\in_array(\trim($subResource, '[]'), $paramKeys)) { + if ($subResource && !\in_array(\trim($subResource, "\[\]"), $paramKeys)) { throw new InvalidArgumentException("{$subResource} is missing from the params."); } diff --git a/src/Appwrite/Event/Mail.php b/src/Appwrite/Event/Mail.php new file mode 100644 index 0000000000..155e853cc5 --- /dev/null +++ b/src/Appwrite/Event/Mail.php @@ -0,0 +1,110 @@ +team = $team; + + return $this; + } + + public function getTeam(): Document + { + return $this->team; + } + + public function setRecipient(string $recipient): self + { + $this->recipient = $recipient; + + return $this; + } + + public function getRecipient(): string + { + return $this->recipient; + } + + public function setUrl(string $url): self + { + $this->url = $url; + + return $this; + } + + public function getURL(): string + { + return $this->url; + } + + public function setType(string $type): self + { + $this->type = $type; + + return $this; + } + + public function getType(): string + { + return $this->type; + } + + public function setName(string $name): self + { + $this->name = $name; + + return $this; + } + + public function getName(): string + { + return $this->name; + } + + public function setLocale(string $locale): self + { + $this->locale = $locale; + + return $this; + } + + public function getLocale(): string + { + return $this->locale; + } + + public function trigger(): string|bool + { + return Resque::enqueue($this->queue, $this->class, [ + 'project' => $this->project, + 'user' => $this->user, + 'payload' => $this->payload, + 'trigger' => $this->trigger, + 'recipient' => $this->recipient, + 'url' => $this->url, + 'locale' => $this->locale, + 'type' => $this->type, + 'name' => $this->name, + 'team' => $this->team, + 'events' => Event::generateEvents($this->getEvent(), $this->getParams()) + ]); + } +} \ No newline at end of file diff --git a/src/Appwrite/Messaging/Adapter/Realtime.php b/src/Appwrite/Messaging/Adapter/Realtime.php index d7d138a790..e2314c4fdd 100644 --- a/src/Appwrite/Messaging/Adapter/Realtime.php +++ b/src/Appwrite/Messaging/Adapter/Realtime.php @@ -246,77 +246,72 @@ class Realtime extends Adapter $roles = []; $permissionsChanged = false; $projectId = null; + $parts = explode('.', $event); - switch (true) { - case strpos($event, 'account.recovery.') === 0: - case strpos($event, 'account.sessions.') === 0: - case strpos($event, 'account.verification.') === 0: + switch ($parts[0]) { + case 'users': $channels[] = 'account'; - $channels[] = 'account.' . $payload->getAttribute('userId'); - $roles = ['user:' . $payload->getAttribute('userId')]; + $channels[] = 'account.' . $parts[1]; + $roles = ['user:' . $parts[1]]; break; - case strpos($event, 'account.') === 0: - $channels[] = 'account'; - $channels[] = 'account.' . $payload->getId(); - $roles = ['user:' . $payload->getId()]; - - break; - case strpos($event, 'teams.memberships') === 0: - $permissionsChanged = in_array($event, ['teams.memberships.update', 'teams.memberships.delete', 'teams.memberships.update.status']); - $channels[] = 'memberships'; - $channels[] = 'memberships.' . $payload->getId(); - $roles = ['team:' . $payload->getAttribute('teamId')]; - - break; - case strpos($event, 'teams.') === 0: - $permissionsChanged = $event === 'teams.create'; - $channels[] = 'teams'; - $channels[] = 'teams.' . $payload->getId(); - $roles = ['team:' . $payload->getId()]; - - break; - case strpos($event, 'database.attributes.') === 0: - case strpos($event, 'database.indexes.') === 0: - $channels[] = 'console'; - $projectId = 'console'; - $roles = ['team:' . $project->getAttribute('teamId')]; - - break; - case strpos($event, 'database.documents.') === 0: - if ($collection->isEmpty()) { - throw new \Exception('Collection needs to be passed to Realtime for Document events in the Database.'); + case 'teams': + if ($parts[2] === 'memberships') { + $permissionsChanged = $parts[4] ?? false; + $channels[] = 'memberships'; + $channels[] = 'memberships.' . $parts[3]; + $roles = ['team:' . $parts[1]]; + } else { + $permissionsChanged = $parts[2] === 'create'; + $channels[] = 'teams'; + $channels[] = 'teams.' . $parts[1]; + $roles = ['team:' . $parts[1]]; } - - $channels[] = 'documents'; - $channels[] = 'collections.' . $payload->getAttribute('$collection') . '.documents'; - $channels[] = 'collections.' . $payload->getAttribute('$collection') . '.documents.' . $payload->getId(); - - $roles = ($collection->getAttribute('permission') === 'collection') ? $collection->getRead() : $payload->getRead(); - break; - case strpos($event, 'storage.files') === 0: - if($bucket->isEmpty()) { - throw new \Exception('Bucket needs to be pased to Realtime for File events in the Storage.'); - } - $channels[] = 'files'; - $channels[] = 'buckets.' . $payload->getAttribute('bucketId') . '.files'; - $channels[] = 'buckets.' . $payload->getAttribute('bucketId') . '.files.' . $payload->getId(); - $roles = $payload->getRead(); - - break; - case strpos($event, 'functions.executions.') === 0: - if (!empty($payload->getRead())) { + case 'collections': + if (in_array($parts[2], ['attributes', 'indexes'])) { $channels[] = 'console'; - $channels[] = 'executions'; - $channels[] = 'executions.' . $payload->getId(); - $channels[] = 'functions.' . $payload->getAttribute('functionId'); - $roles = $payload->getRead(); + $projectId = 'console'; + $roles = ['team:' . $project->getAttribute('teamId')]; + } elseif ($parts[2] === 'documents') { + if ($collection->isEmpty()) { + throw new \Exception('Collection needs to be passed to Realtime for Document events in the Database.'); + } + + $channels[] = 'documents'; + $channels[] = 'collections.' . $payload->getCollection() . '.documents'; + $channels[] = 'collections.' . $payload->getCollection() . '.documents.' . $payload->getId(); + + $roles = ($collection->getAttribute('permission') === 'collection') ? $collection->getRead() : $payload->getRead(); } break; - case strpos($event, 'functions.deployments.') === 0: - $channels[] = 'console'; - $roles = ['team:' . $project->getAttribute('teamId')]; + case 'buckets': + if ($parts[2] === 'files') { + if($bucket->isEmpty()) { + throw new \Exception('Bucket needs to be pased to Realtime for File events in the Storage.'); + } + $channels[] = 'files'; + $channels[] = 'buckets.' . $payload->getAttribute('bucketId') . '.files'; + $channels[] = 'buckets.' . $payload->getAttribute('bucketId') . '.files.' . $payload->getId(); + $roles = ($bucket->getAttribute('permission') === 'collection') ? $bucket->getRead() : $payload->getRead(); + } + + break; + + case 'functions': + if ($parts[2] === 'executions') { + if (!empty($payload->getRead())) { + $channels[] = 'console'; + $channels[] = 'executions'; + $channels[] = 'executions.' . $payload->getId(); + $channels[] = 'functions.' . $payload->getAttribute('functionId'); + $roles = $payload->getRead(); + } + } elseif ($parts[2] === 'deployments') { + $channels[] = 'console'; + $roles = ['team:' . $project->getAttribute('teamId')]; + } + break; } diff --git a/tests/e2e/Services/Functions/FunctionsConsoleClientTest.php b/tests/e2e/Services/Functions/FunctionsConsoleClientTest.php index a0f6cca5e6..0180a87de3 100644 --- a/tests/e2e/Services/Functions/FunctionsConsoleClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsConsoleClientTest.php @@ -28,8 +28,8 @@ class FunctionsConsoleClientTest extends Scope 'funcKey3' => 'funcValue3', ], 'events' => [ - 'account.create', - 'account.delete', + 'users.*.create', + 'users.*.delete', ], 'schedule' => '0 0 1 1 *', 'timeout' => 10, diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index d2443783c0..10645a25ca 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -33,8 +33,8 @@ class FunctionsCustomClientTest extends Scope 'funcKey3' => 'funcValue3', ], 'events' => [ - 'account.create', - 'account.delete', + 'users.*.create', + 'users.*.delete', ], 'schedule' => '0 0 1 1 *', 'timeout' => 10, @@ -65,8 +65,8 @@ class FunctionsCustomClientTest extends Scope 'funcKey3' => 'funcValue3', ], 'events' => [ - 'account.create', - 'account.delete', + 'users.*.create', + 'users.*.delete', ], 'schedule' => '0 0 1 1 *', 'timeout' => 10, diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index e26f93ad34..263da52aa1 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -16,7 +16,7 @@ class FunctionsCustomServerTest extends Scope use ProjectCustom; use SideServer; - public function testCreate():array + public function testCreate(): array { /** * Test for SUCCESS @@ -34,8 +34,8 @@ class FunctionsCustomServerTest extends Scope 'funcKey3' => 'funcValue3', ], 'events' => [ - 'account.create', - 'account.delete', + 'users.*.create', + 'users.*.delete', ], 'schedule' => '0 0 1 1 *', 'timeout' => 10, @@ -56,12 +56,12 @@ class FunctionsCustomServerTest extends Scope 'funcKey3' => 'funcValue3', ], $response1['body']['vars']); $this->assertEquals([ - 'account.create', - 'account.delete', + 'users.*.create', + 'users.*.delete', ], $response1['body']['events']); $this->assertEquals('0 0 1 1 *', $response1['body']['schedule']); $this->assertEquals(10, $response1['body']['timeout']); - + /** * Test for FAILURE */ @@ -74,7 +74,7 @@ class FunctionsCustomServerTest extends Scope /** * @depends testCreate */ - public function testList(array $data):array + public function testList(array $data): array { /** * Test for SUCCESS @@ -132,8 +132,8 @@ class FunctionsCustomServerTest extends Scope 'funcKey3' => 'funcValue3', ], 'events' => [ - 'account.create', - 'account.delete', + 'users.*.create', + 'users.*.delete', ], 'schedule' => '0 0 1 1 *', 'timeout' => 10, @@ -193,7 +193,7 @@ class FunctionsCustomServerTest extends Scope /** * @depends testList */ - public function testGet(array $data):array + public function testGet(array $data): array { /** * Test for SUCCESS @@ -222,12 +222,12 @@ class FunctionsCustomServerTest extends Scope /** * @depends testGet */ - public function testUpdate($data):array + public function testUpdate($data): array { /** * Test for SUCCESS */ - $response1 = $this->client->call(Client::METHOD_PUT, '/functions/'.$data['functionId'], array_merge([ + $response1 = $this->client->call(Client::METHOD_PUT, '/functions/' . $data['functionId'], array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -238,8 +238,8 @@ class FunctionsCustomServerTest extends Scope 'key6' => 'value6', ], 'events' => [ - 'account.update.name', - 'account.update.email', + 'users.*.update.name', + 'users.*.update.email', ], 'schedule' => '0 0 1 1 *', 'timeout' => 5, @@ -257,12 +257,12 @@ class FunctionsCustomServerTest extends Scope 'key6' => 'value6', ], $response1['body']['vars']); $this->assertEquals([ - 'account.update.name', - 'account.update.email', + 'users.*.update.name', + 'users.*.update.email', ], $response1['body']['events']); $this->assertEquals('0 0 1 1 *', $response1['body']['schedule']); $this->assertEquals(5, $response1['body']['timeout']); - + /** * Test for FAILURE */ @@ -273,16 +273,16 @@ class FunctionsCustomServerTest extends Scope /** * @depends testUpdate */ - public function testCreateDeployment($data):array + public function testCreateDeployment($data): array { /** * Test for SUCCESS */ $folder = 'php'; - $code = realpath(__DIR__ . '/../../../resources/functions'). "/$folder/code.tar.gz"; + $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; $this->packageCode($folder); - $deployment = $this->client->call(Client::METHOD_POST, '/functions/'.$data['functionId'].'/deployments', array_merge([ + $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/deployments', array_merge([ 'content-type' => 'multipart/form-data', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -306,16 +306,17 @@ class FunctionsCustomServerTest extends Scope /** * @depends testUpdate */ - public function testCreateDeploymentLarge($data): array { + public function testCreateDeploymentLarge($data): array + { /** * Test for Large Code File SUCCESS */ $folder = 'php-large'; - $code = realpath(__DIR__ . '/../../../resources/functions'). "/$folder/code.tar.gz"; + $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; $this->packageCode($folder); - $chunkSize = 5*1024*1024; + $chunkSize = 5 * 1024 * 1024; $handle = @fopen($code, "rb"); $mimeType = 'application/x-gzip'; $counter = 0; @@ -328,10 +329,10 @@ class FunctionsCustomServerTest extends Scope while (!feof($handle)) { $curlFile = new \CURLFile('data://' . $mimeType . ';base64,' . base64_encode(@fread($handle, $chunkSize)), $mimeType, 'php-large-fx.tar.gz'); $headers['content-range'] = 'bytes ' . ($counter * $chunkSize) . '-' . min(((($counter * $chunkSize) + $chunkSize) - 1), $size) . '/' . $size; - if(!empty($id)) { + if (!empty($id)) { $headers['x-appwrite-id'] = $id; } - $largeTag = $this->client->call(Client::METHOD_POST, '/functions/'.$data['functionId'].'/deployments', array_merge($headers, $this->getHeaders()), [ + $largeTag = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/deployments', array_merge($headers, $this->getHeaders()), [ 'entrypoint' => 'index.php', 'code' => $curlFile, ]); @@ -345,19 +346,19 @@ class FunctionsCustomServerTest extends Scope $this->assertIsInt($largeTag['body']['dateCreated']); $this->assertEquals('index.php', $largeTag['body']['entrypoint']); $this->assertGreaterThan(10000, $largeTag['body']['size']); - + return $data; } /** * @depends testCreateDeployment */ - public function testUpdateDeployment($data):array + public function testUpdateDeployment($data): array { /** * Test for SUCCESS */ - $response = $this->client->call(Client::METHOD_PATCH, '/functions/'.$data['functionId'].'/deployments/'.$data['deploymentId'], array_merge([ + $response = $this->client->call(Client::METHOD_PATCH, '/functions/' . $data['functionId'] . '/deployments/' . $data['deploymentId'], array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -367,7 +368,7 @@ class FunctionsCustomServerTest extends Scope $this->assertIsInt($response['body']['dateCreated']); $this->assertIsInt($response['body']['dateUpdated']); $this->assertEquals($data['deploymentId'], $response['body']['deployment']); - + /** * Test for FAILURE */ @@ -378,12 +379,12 @@ class FunctionsCustomServerTest extends Scope /** * @depends testCreateDeployment */ - public function testListDeployments(array $data):array + public function testListDeployments(array $data): array { /** * Test for SUCCESS */ - $function = $this->client->call(Client::METHOD_GET, '/functions/'.$data['functionId'].'/deployments', array_merge([ + $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -396,7 +397,7 @@ class FunctionsCustomServerTest extends Scope /** * Test search queries */ - $function = $this->client->call(Client::METHOD_GET, '/functions/'.$data['functionId'].'/deployments', array_merge([ + $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders(), [ @@ -409,7 +410,7 @@ class FunctionsCustomServerTest extends Scope $this->assertCount(2, $function['body']['deployments']); $this->assertEquals($function['body']['deployments'][0]['$id'], $data['deploymentId']); - $function = $this->client->call(Client::METHOD_GET, '/functions/'.$data['functionId'].'/deployments', array_merge([ + $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders(), [ @@ -422,7 +423,7 @@ class FunctionsCustomServerTest extends Scope $this->assertCount(2, $function['body']['deployments']); $this->assertEquals($function['body']['deployments'][0]['$id'], $data['deploymentId']); - $function = $this->client->call(Client::METHOD_GET, '/functions/'.$data['functionId'].'/deployments', array_merge([ + $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders(), [ @@ -441,12 +442,12 @@ class FunctionsCustomServerTest extends Scope /** * @depends testCreateDeployment */ - public function testGetDeployment(array $data):array + public function testGetDeployment(array $data): array { /** * Test for SUCCESS */ - $function = $this->client->call(Client::METHOD_GET, '/functions/'.$data['functionId'].'/deployments/' . $data['deploymentId'], array_merge([ + $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments/' . $data['deploymentId'], array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -456,7 +457,7 @@ class FunctionsCustomServerTest extends Scope /** * Test for FAILURE */ - $function = $this->client->call(Client::METHOD_GET, '/functions/'.$data['functionId'].'/deployments/x', array_merge([ + $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments/x', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -469,12 +470,12 @@ class FunctionsCustomServerTest extends Scope /** * @depends testUpdateDeployment */ - public function testCreateExecution($data):array + public function testCreateExecution($data): array { /** * Test for SUCCESS */ - $execution = $this->client->call(Client::METHOD_POST, '/functions/'.$data['functionId'].'/executions', array_merge([ + $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/executions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -496,7 +497,7 @@ class FunctionsCustomServerTest extends Scope sleep(5); - $execution = $this->client->call(Client::METHOD_GET, '/functions/'.$data['functionId'].'/executions/'.$executionId, array_merge([ + $execution = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/executions/' . $executionId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -529,12 +530,12 @@ class FunctionsCustomServerTest extends Scope /** * @depends testCreateExecution */ - public function testListExecutions(array $data):array + public function testListExecutions(array $data): array { /** * Test for SUCCESS */ - $function = $this->client->call(Client::METHOD_GET, '/functions/'.$data['functionId'].'/executions', array_merge([ + $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/executions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -549,7 +550,7 @@ class FunctionsCustomServerTest extends Scope * Test search queries */ - $response = $this->client->call(Client::METHOD_GET, '/functions/'.$data['functionId'].'/executions', array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/executions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -562,7 +563,7 @@ class FunctionsCustomServerTest extends Scope $this->assertCount(1, $response['body']['executions']); $this->assertEquals($data['functionId'], $response['body']['executions'][0]['functionId']); - $response = $this->client->call(Client::METHOD_GET, '/functions/'.$data['functionId'].'/executions', array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/executions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -581,12 +582,12 @@ class FunctionsCustomServerTest extends Scope /** * @depends testUpdateDeployment */ - public function testSyncCreateExecution($data):array + public function testSyncCreateExecution($data): array { /** * Test for SUCCESS */ - $execution = $this->client->call(Client::METHOD_POST, '/functions/'.$data['functionId'].'/executions', array_merge([ + $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/executions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -610,12 +611,12 @@ class FunctionsCustomServerTest extends Scope /** * @depends testListExecutions */ - public function testGetExecution(array $data):array + public function testGetExecution(array $data): array { /** * Test for SUCCESS */ - $function = $this->client->call(Client::METHOD_GET, '/functions/'.$data['functionId'].'/executions/' . $data['executionId'], array_merge([ + $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/executions/' . $data['executionId'], array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -626,7 +627,7 @@ class FunctionsCustomServerTest extends Scope /** * Test for FAILURE */ - $function = $this->client->call(Client::METHOD_GET, '/functions/'.$data['functionId'].'/executions/x', array_merge([ + $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/executions/x', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -639,12 +640,12 @@ class FunctionsCustomServerTest extends Scope /** * @depends testGetExecution */ - public function testDeleteDeployment($data):array + public function testDeleteDeployment($data): array { /** * Test for SUCCESS */ - $function = $this->client->call(Client::METHOD_DELETE, '/functions/'.$data['functionId'].'/deployments/' . $data['deploymentId'], array_merge([ + $function = $this->client->call(Client::METHOD_DELETE, '/functions/' . $data['functionId'] . '/deployments/' . $data['deploymentId'], array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -652,11 +653,11 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(204, $function['headers']['status-code']); $this->assertEmpty($function['body']); - $function = $this->client->call(Client::METHOD_GET, '/functions/'.$data['functionId'].'/deployments/' . $data['deploymentId'], array_merge([ + $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments/' . $data['deploymentId'], array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); - + $this->assertEquals(404, $function['headers']['status-code']); /** @@ -669,12 +670,12 @@ class FunctionsCustomServerTest extends Scope /** * @depends testCreateDeployment */ - public function testDelete($data):array + public function testDelete($data): array { /** * Test for SUCCESS */ - $function = $this->client->call(Client::METHOD_DELETE, '/functions/'. $data['functionId'], array_merge([ + $function = $this->client->call(Client::METHOD_DELETE, '/functions/' . $data['functionId'], array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -686,7 +687,7 @@ class FunctionsCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); - + $this->assertEquals(404, $function['headers']['status-code']); /** @@ -702,15 +703,15 @@ class FunctionsCustomServerTest extends Scope $entrypoint = 'index.php'; $timeout = 2; $folder = 'timeout'; - $code = realpath(__DIR__ . '/../../../resources/functions'). "/$folder/code.tar.gz"; + $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; $this->packageCode($folder); - + $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'functionId' => 'unique()', - 'name' => 'Test '.$name, + 'name' => 'Test ' . $name, 'runtime' => $name, 'vars' => [], 'events' => [], @@ -722,7 +723,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(201, $function['headers']['status-code']); - $deployment = $this->client->call(Client::METHOD_POST, '/functions/'.$functionId.'/deployments', array_merge([ + $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([ 'content-type' => 'multipart/form-data', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -735,8 +736,8 @@ class FunctionsCustomServerTest extends Scope // Allow build step to run sleep(20); - - $execution = $this->client->call(Client::METHOD_POST, '/functions/'.$functionId.'/executions', array_merge([ + + $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -744,12 +745,12 @@ class FunctionsCustomServerTest extends Scope ]); $executionId = $execution['body']['$id'] ?? ''; - + $this->assertEquals(201, $execution['headers']['status-code']); sleep(5); - $executions = $this->client->call(Client::METHOD_GET, '/functions/'.$functionId.'/executions', array_merge([ + $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -768,7 +769,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals($executions['body']['executions'][0]['stderr'], 'Execution timed out.'); // Cleanup : Delete function - $response = $this->client->call(Client::METHOD_DELETE, '/functions/'. $functionId, [ + $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], @@ -786,7 +787,7 @@ class FunctionsCustomServerTest extends Scope $entrypoint = 'index.php'; $timeout = 2; $folder = 'php-fn'; - $code = realpath(__DIR__ . '/../../../resources/functions'). "/$folder/code.tar.gz"; + $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; $this->packageCode($folder); $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ @@ -794,7 +795,7 @@ class FunctionsCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'functionId' => 'unique()', - 'name' => 'Test '.$name, + 'name' => 'Test ' . $name, 'runtime' => $name, 'vars' => [], 'events' => [], @@ -806,7 +807,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(201, $function['headers']['status-code']); - $deployment = $this->client->call(Client::METHOD_POST, '/functions/'.$functionId.'/deployments', array_merge([ + $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([ 'content-type' => 'multipart/form-data', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -820,14 +821,14 @@ class FunctionsCustomServerTest extends Scope // Allow build step to run sleep(10); - $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/'.$functionId.'/deployments/'.$deploymentId, array_merge([ + $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); $this->assertEquals(200, $deployment['headers']['status-code']); - - $execution = $this->client->call(Client::METHOD_POST, '/functions/'.$functionId.'/executions', array_merge([ + + $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -835,14 +836,14 @@ class FunctionsCustomServerTest extends Scope ]); $executionId = $execution['body']['$id'] ?? ''; - + $this->assertEquals(201, $execution['headers']['status-code']); $executionId = $execution['body']['$id'] ?? ''; - + sleep(10); - $executions = $this->client->call(Client::METHOD_GET, '/functions/'.$functionId.'/executions/'.$executionId, array_merge([ + $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -852,7 +853,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(200, $executions['headers']['status-code']); $this->assertEquals('completed', $executions['body']['status']); $this->assertEquals($functionId, $output['APPWRITE_FUNCTION_ID']); - $this->assertEquals('Test '.$name, $output['APPWRITE_FUNCTION_NAME']); + $this->assertEquals('Test ' . $name, $output['APPWRITE_FUNCTION_NAME']); $this->assertEquals($deploymentId, $output['APPWRITE_FUNCTION_DEPLOYMENT']); $this->assertEquals('http', $output['APPWRITE_FUNCTION_TRIGGER']); $this->assertEquals('PHP', $output['APPWRITE_FUNCTION_RUNTIME_NAME']); @@ -864,11 +865,11 @@ class FunctionsCustomServerTest extends Scope $this->assertEmpty($output['APPWRITE_FUNCTION_JWT']); $this->assertEquals($this->getProject()['$id'], $output['APPWRITE_FUNCTION_PROJECT_ID']); - $executions = $this->client->call(Client::METHOD_GET, '/functions/'.$functionId.'/executions', array_merge([ + $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); - + $this->assertEquals($executions['headers']['status-code'], 200); $this->assertEquals($executions['body']['total'], 1); $this->assertIsArray($executions['body']['executions']); @@ -878,7 +879,7 @@ class FunctionsCustomServerTest extends Scope $this->assertStringContainsString('foobar', $executions['body']['executions'][0]['stdout']); // Cleanup : Delete function - $response = $this->client->call(Client::METHOD_DELETE, '/functions/'. $functionId, [ + $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], @@ -892,9 +893,9 @@ class FunctionsCustomServerTest extends Scope { $name = 'node-17.0'; $folder = 'node'; - $code = realpath(__DIR__ . '/../../../resources/functions'). "/$folder/code.tar.gz"; + $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; $this->packageCode($folder); - + $entrypoint = 'index.js'; $timeout = 2; @@ -903,7 +904,7 @@ class FunctionsCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'functionId' => 'unique()', - 'name' => 'Test '.$name, + 'name' => 'Test ' . $name, 'runtime' => $name, 'vars' => [ 'CUSTOM_VARIABLE' => 'variable', @@ -917,7 +918,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(201, $function['headers']['status-code']); - $deployment = $this->client->call(Client::METHOD_POST, '/functions/'.$functionId.'/deployments', array_merge([ + $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([ 'content-type' => 'multipart/form-data', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -932,7 +933,7 @@ class FunctionsCustomServerTest extends Scope // Allow build step to run sleep(10); - $execution = $this->client->call(Client::METHOD_POST, '/functions/'.$functionId.'/executions', array_merge([ + $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -940,14 +941,14 @@ class FunctionsCustomServerTest extends Scope ]); $executionId = $execution['body']['$id'] ?? ''; - + $this->assertEquals(201, $execution['headers']['status-code']); $executionId = $execution['body']['$id'] ?? ''; - + sleep(10); - $executions = $this->client->call(Client::METHOD_GET, '/functions/'.$functionId.'/executions/'.$executionId, array_merge([ + $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -957,7 +958,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(200, $executions['headers']['status-code']); $this->assertEquals('completed', $executions['body']['status']); $this->assertEquals($functionId, $output['APPWRITE_FUNCTION_ID']); - $this->assertEquals('Test '.$name, $output['APPWRITE_FUNCTION_NAME']); + $this->assertEquals('Test ' . $name, $output['APPWRITE_FUNCTION_NAME']); $this->assertEquals($deploymentId, $output['APPWRITE_FUNCTION_DEPLOYMENT']); $this->assertEquals('http', $output['APPWRITE_FUNCTION_TRIGGER']); $this->assertEquals('Node.js', $output['APPWRITE_FUNCTION_RUNTIME_NAME']); @@ -970,11 +971,11 @@ class FunctionsCustomServerTest extends Scope $this->assertEmpty($output['APPWRITE_FUNCTION_JWT']); $this->assertEquals($this->getProject()['$id'], $output['APPWRITE_FUNCTION_PROJECT_ID']); - $executions = $this->client->call(Client::METHOD_GET, '/functions/'.$functionId.'/executions', array_merge([ + $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); - + $this->assertEquals($executions['headers']['status-code'], 200); $this->assertEquals($executions['body']['total'], 1); $this->assertIsArray($executions['body']['executions']); @@ -984,7 +985,7 @@ class FunctionsCustomServerTest extends Scope $this->assertStringContainsString('foobar', $executions['body']['executions'][0]['stdout']); // Cleanup : Delete function - $response = $this->client->call(Client::METHOD_DELETE, '/functions/'. $functionId, [ + $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], @@ -997,7 +998,7 @@ class FunctionsCustomServerTest extends Scope { $name = 'python-3.9'; $folder = 'python'; - $code = realpath(__DIR__ . '/../../../resources/functions'). "/$folder/code.tar.gz"; + $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; $this->packageCode($folder); $entrypoint = 'main.py'; @@ -1008,7 +1009,7 @@ class FunctionsCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'functionId' => 'unique()', - 'name' => 'Test '.$name, + 'name' => 'Test ' . $name, 'runtime' => $name, 'vars' => [ 'CUSTOM_VARIABLE' => 'variable', @@ -1022,7 +1023,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(201, $function['headers']['status-code']); - $deployment = $this->client->call(Client::METHOD_POST, '/functions/'.$functionId.'/deployments', array_merge([ + $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([ 'content-type' => 'multipart/form-data', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -1037,7 +1038,7 @@ class FunctionsCustomServerTest extends Scope // Allow build step to run sleep(30); - $execution = $this->client->call(Client::METHOD_POST, '/functions/'.$functionId.'/executions', array_merge([ + $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -1045,14 +1046,14 @@ class FunctionsCustomServerTest extends Scope ]); $executionId = $execution['body']['$id'] ?? ''; - + $this->assertEquals(201, $execution['headers']['status-code']); $executionId = $execution['body']['$id'] ?? ''; - + sleep(30); - $executions = $this->client->call(Client::METHOD_GET, '/functions/'.$functionId.'/executions/'.$executionId, array_merge([ + $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -1062,7 +1063,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(200, $executions['headers']['status-code']); $this->assertEquals('completed', $executions['body']['status']); $this->assertEquals($functionId, $output['APPWRITE_FUNCTION_ID']); - $this->assertEquals('Test '.$name, $output['APPWRITE_FUNCTION_NAME']); + $this->assertEquals('Test ' . $name, $output['APPWRITE_FUNCTION_NAME']); $this->assertEquals($deploymentId, $output['APPWRITE_FUNCTION_DEPLOYMENT']); $this->assertEquals('http', $output['APPWRITE_FUNCTION_TRIGGER']); $this->assertEquals('Python', $output['APPWRITE_FUNCTION_RUNTIME_NAME']); @@ -1075,11 +1076,11 @@ class FunctionsCustomServerTest extends Scope $this->assertEmpty($output['APPWRITE_FUNCTION_JWT']); $this->assertEquals($this->getProject()['$id'], $output['APPWRITE_FUNCTION_PROJECT_ID']); - $executions = $this->client->call(Client::METHOD_GET, '/functions/'.$functionId.'/executions', array_merge([ + $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); - + $this->assertEquals($executions['headers']['status-code'], 200); $this->assertEquals($executions['body']['total'], 1); $this->assertIsArray($executions['body']['executions']); @@ -1089,7 +1090,7 @@ class FunctionsCustomServerTest extends Scope $this->assertStringContainsString('foobar', $executions['body']['executions'][0]['stdout']); // Cleanup : Delete function - $response = $this->client->call(Client::METHOD_DELETE, '/functions/'. $functionId, [ + $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], @@ -1102,9 +1103,9 @@ class FunctionsCustomServerTest extends Scope { $name = 'dart-2.15'; $folder = 'dart'; - $code = realpath(__DIR__ . '/../../../resources/functions'). "/$folder/code.tar.gz"; + $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; $this->packageCode($folder); - + $entrypoint = 'main.dart'; $timeout = 2; @@ -1113,7 +1114,7 @@ class FunctionsCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'functionId' => 'unique()', - 'name' => 'Test '.$name, + 'name' => 'Test ' . $name, 'runtime' => $name, 'vars' => [ 'CUSTOM_VARIABLE' => 'variable', @@ -1127,7 +1128,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(201, $function['headers']['status-code']); - $deployment = $this->client->call(Client::METHOD_POST, '/functions/'.$functionId.'/deployments', array_merge([ + $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([ 'content-type' => 'multipart/form-data', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -1142,7 +1143,7 @@ class FunctionsCustomServerTest extends Scope // Allow build step to run sleep(40); - $execution = $this->client->call(Client::METHOD_POST, '/functions/'.$functionId.'/executions', array_merge([ + $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -1150,14 +1151,14 @@ class FunctionsCustomServerTest extends Scope ]); $executionId = $execution['body']['$id'] ?? ''; - + $this->assertEquals(201, $execution['headers']['status-code']); $executionId = $execution['body']['$id'] ?? ''; - + sleep(10); - $executions = $this->client->call(Client::METHOD_GET, '/functions/'.$functionId.'/executions/'.$executionId, array_merge([ + $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -1167,7 +1168,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(200, $executions['headers']['status-code']); $this->assertEquals('completed', $executions['body']['status']); $this->assertEquals($functionId, $output['APPWRITE_FUNCTION_ID']); - $this->assertEquals('Test '.$name, $output['APPWRITE_FUNCTION_NAME']); + $this->assertEquals('Test ' . $name, $output['APPWRITE_FUNCTION_NAME']); $this->assertEquals($deploymentId, $output['APPWRITE_FUNCTION_DEPLOYMENT']); $this->assertEquals('http', $output['APPWRITE_FUNCTION_TRIGGER']); $this->assertEquals('Dart', $output['APPWRITE_FUNCTION_RUNTIME_NAME']); @@ -1180,11 +1181,11 @@ class FunctionsCustomServerTest extends Scope $this->assertEmpty($output['APPWRITE_FUNCTION_JWT']); $this->assertEquals($this->getProject()['$id'], $output['APPWRITE_FUNCTION_PROJECT_ID']); - $executions = $this->client->call(Client::METHOD_GET, '/functions/'.$functionId.'/executions', array_merge([ + $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); - + $this->assertEquals($executions['headers']['status-code'], 200); $this->assertEquals($executions['body']['total'], 1); $this->assertIsArray($executions['body']['executions']); @@ -1194,7 +1195,7 @@ class FunctionsCustomServerTest extends Scope $this->assertStringContainsString('foobar', $executions['body']['executions'][0]['stdout']); // Cleanup : Delete function - $response = $this->client->call(Client::METHOD_DELETE, '/functions/'. $functionId, [ + $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], @@ -1207,9 +1208,9 @@ class FunctionsCustomServerTest extends Scope { $name = 'ruby-3.1'; $folder = 'ruby'; - $code = realpath(__DIR__ . '/../../../resources/functions'). "/$folder/code.tar.gz"; + $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; $this->packageCode($folder); - + $entrypoint = 'main.rb'; $timeout = 2; @@ -1218,7 +1219,7 @@ class FunctionsCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'functionId' => 'unique()', - 'name' => 'Test '.$name, + 'name' => 'Test ' . $name, 'runtime' => $name, 'vars' => [ 'CUSTOM_VARIABLE' => 'variable', @@ -1232,7 +1233,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(201, $function['headers']['status-code']); - $deployment = $this->client->call(Client::METHOD_POST, '/functions/'.$functionId.'/deployments', array_merge([ + $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([ 'content-type' => 'multipart/form-data', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -1247,7 +1248,7 @@ class FunctionsCustomServerTest extends Scope // Allow build step to run sleep(30); - $execution = $this->client->call(Client::METHOD_POST, '/functions/'.$functionId.'/executions', array_merge([ + $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -1255,14 +1256,14 @@ class FunctionsCustomServerTest extends Scope ]); $executionId = $execution['body']['$id'] ?? ''; - + $this->assertEquals(201, $execution['headers']['status-code']); $executionId = $execution['body']['$id'] ?? ''; - + sleep(10); - $executions = $this->client->call(Client::METHOD_GET, '/functions/'.$functionId.'/executions/'.$executionId, array_merge([ + $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -1272,7 +1273,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(200, $executions['headers']['status-code']); $this->assertEquals('completed', $executions['body']['status']); $this->assertEquals($functionId, $output['APPWRITE_FUNCTION_ID']); - $this->assertEquals('Test '.$name, $output['APPWRITE_FUNCTION_NAME']); + $this->assertEquals('Test ' . $name, $output['APPWRITE_FUNCTION_NAME']); $this->assertEquals($deploymentId, $output['APPWRITE_FUNCTION_DEPLOYMENT']); $this->assertEquals('http', $output['APPWRITE_FUNCTION_TRIGGER']); $this->assertEquals('Ruby', $output['APPWRITE_FUNCTION_RUNTIME_NAME']); @@ -1285,11 +1286,11 @@ class FunctionsCustomServerTest extends Scope $this->assertEmpty($output['APPWRITE_FUNCTION_JWT']); $this->assertEquals($this->getProject()['$id'], $output['APPWRITE_FUNCTION_PROJECT_ID']); - $executions = $this->client->call(Client::METHOD_GET, '/functions/'.$functionId.'/executions', array_merge([ + $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); - + $this->assertEquals($executions['headers']['status-code'], 200); $this->assertEquals($executions['body']['total'], 1); $this->assertIsArray($executions['body']['executions']); @@ -1299,7 +1300,7 @@ class FunctionsCustomServerTest extends Scope $this->assertStringContainsString('foobar', $executions['body']['executions'][0]['stdout']); // Cleanup : Delete function - $response = $this->client->call(Client::METHOD_DELETE, '/functions/'. $functionId, [ + $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], @@ -1314,7 +1315,7 @@ class FunctionsCustomServerTest extends Scope // $folder = 'swift'; // $code = realpath(__DIR__ . '/../../../resources/functions'). "/$folder/code.tar.gz"; // $this->packageCode($folder); - + // $entrypoint = 'index.swift'; // $timeout = 5; @@ -1360,11 +1361,11 @@ class FunctionsCustomServerTest extends Scope // ]); // $executionId = $execution['body']['$id'] ?? ''; - + // $this->assertEquals(201, $execution['headers']['status-code']); // $executionId = $execution['body']['$id'] ?? ''; - + // sleep(10); // $executions = $this->client->call(Client::METHOD_GET, '/functions/'.$functionId.'/executions/'.$executionId, array_merge([ @@ -1394,7 +1395,7 @@ class FunctionsCustomServerTest extends Scope // 'content-type' => 'application/json', // 'x-appwrite-project' => $this->getProject()['$id'], // ], $this->getHeaders())); - + // $this->assertEquals($executions['headers']['status-code'], 200); // $this->assertEquals($executions['body']['total'], 1); // $this->assertIsArray($executions['body']['executions']); @@ -1432,6 +1433,5 @@ class FunctionsCustomServerTest extends Scope $this->assertArrayHasKey('image', $runtime); $this->assertArrayHasKey('base', $runtime); $this->assertArrayHasKey('supports', $runtime); - } -} \ No newline at end of file +} diff --git a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php index c5155a97a8..194ccebdaa 100644 --- a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php +++ b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php @@ -278,7 +278,7 @@ class RealtimeCustomClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertContains('account', $response['data']['channels']); $this->assertContains('account.' . $userId, $response['data']['channels']); - $this->assertEquals('account.update.name', $response['data']['event']); + $this->assertEquals("users.{$userId}.update.name", $response['data']['event']); $this->assertNotEmpty($response['data']['payload']); $this->assertEquals($name, $response['data']['payload']['name']); @@ -307,7 +307,7 @@ class RealtimeCustomClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertContains('account', $response['data']['channels']); $this->assertContains('account.' . $userId, $response['data']['channels']); - $this->assertEquals('account.update.password', $response['data']['event']); + $this->assertEquals("users.{$userId}.update.password", $response['data']['event']); $this->assertNotEmpty($response['data']['payload']); $this->assertEquals($name, $response['data']['payload']['name']); @@ -335,7 +335,7 @@ class RealtimeCustomClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertContains('account', $response['data']['channels']); $this->assertContains('account.' . $userId, $response['data']['channels']); - $this->assertEquals('account.update.email', $response['data']['event']); + $this->assertEquals("users.{$userId}.update.email", $response['data']['event']); $this->assertNotEmpty($response['data']['payload']); $this->assertEquals('torsten@appwrite.io', $response['data']['payload']['email']); @@ -343,7 +343,7 @@ class RealtimeCustomClientTest extends Scope /** * Test Account Verification Create */ - $this->client->call(Client::METHOD_POST, '/account/verification', array_merge([ + $verification = $this->client->call(Client::METHOD_POST, '/account/verification', array_merge([ 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $projectId, @@ -362,7 +362,7 @@ class RealtimeCustomClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertContains('account', $response['data']['channels']); $this->assertContains('account.' . $userId, $response['data']['channels']); - $this->assertEquals('account.verification.create', $response['data']['event']); + $this->assertEquals("users.{$userId}.verification.{$verification['body']['$id']}.create", $response['data']['event']); $lastEmail = $this->getLastEmail(); $verification = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 256); @@ -370,7 +370,7 @@ class RealtimeCustomClientTest extends Scope /** * Test Account Verification Complete */ - $response = $this->client->call(Client::METHOD_PUT, '/account/verification', array_merge([ + $verification = $this->client->call(Client::METHOD_PUT, '/account/verification', array_merge([ 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $projectId, @@ -390,7 +390,7 @@ class RealtimeCustomClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertContains('account', $response['data']['channels']); $this->assertContains('account.' . $userId, $response['data']['channels']); - $this->assertEquals('account.verification.update', $response['data']['event']); + $this->assertEquals("users.{$userId}.verification.{$verification['body']['$id']}.update", $response['data']['event']); /** * Test Acoount Prefs Update @@ -417,7 +417,7 @@ class RealtimeCustomClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertContains('account', $response['data']['channels']); $this->assertContains('account.' . $userId, $response['data']['channels']); - $this->assertEquals('account.update.prefs', $response['data']['event']); + $this->assertEquals("users.{$userId}.update.prefs", $response['data']['event']); $this->assertNotEmpty($response['data']['payload']); /** @@ -445,7 +445,7 @@ class RealtimeCustomClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertContains('account', $response['data']['channels']); $this->assertContains('account.' . $userId, $response['data']['channels']); - $this->assertEquals('account.sessions.create', $response['data']['event']); + $this->assertEquals("users.{$userId}.sessions.{$sessionNewId}.create", $response['data']['event']); $this->assertNotEmpty($response['data']['payload']); /** @@ -468,13 +468,13 @@ class RealtimeCustomClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertContains('account', $response['data']['channels']); $this->assertContains('account.' . $userId, $response['data']['channels']); - $this->assertEquals('account.sessions.delete', $response['data']['event']); + $this->assertEquals("users.{$userId}.sessions.{$sessionNewId}.delete", $response['data']['event']); $this->assertNotEmpty($response['data']['payload']); /** * Test Account Create Recovery */ - $this->client->call(Client::METHOD_POST, '/account/recovery', array_merge([ + $recovery = $this->client->call(Client::METHOD_POST, '/account/recovery', array_merge([ 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $projectId, @@ -482,7 +482,7 @@ class RealtimeCustomClientTest extends Scope 'email' => 'torsten@appwrite.io', 'url' => 'http://localhost/recovery', ]); - + $recoveryId = $recovery['body']['$id']; $response = json_decode($client->receive(), true); $lastEmail = $this->getLastEmail(); @@ -496,7 +496,7 @@ class RealtimeCustomClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertContains('account', $response['data']['channels']); $this->assertContains('account.' . $userId, $response['data']['channels']); - $this->assertEquals('account.recovery.create', $response['data']['event']); + $this->assertEquals("users.{$userId}.recovery.{$recoveryId}.create", $response['data']['event']); $this->assertNotEmpty($response['data']['payload']); $response = $this->client->call(Client::METHOD_PUT, '/account/recovery', array_merge([ @@ -520,7 +520,7 @@ class RealtimeCustomClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertContains('account', $response['data']['channels']); $this->assertContains('account.' . $userId, $response['data']['channels']); - $this->assertEquals('account.recovery.update', $response['data']['event']); + $this->assertEquals("users.{$userId}.recovery.{$recoveryId}.update", $response['data']['event']); $this->assertNotEmpty($response['data']['payload']); $client->close(); @@ -610,7 +610,7 @@ class RealtimeCustomClientTest extends Scope $this->assertContains('documents', $response['data']['channels']); $this->assertContains('collections.' . $data['actorsId'] . '.documents.' . $document['body']['$id'], $response['data']['channels']); $this->assertContains('collections.' . $actors['body']['$id'] . '.documents', $response['data']['channels']); - $this->assertEquals('database.documents.create', $response['data']['event']); + $this->assertEquals("collections.{$data['actorsId']}.documents.{$document['body']['$id']}.create", $response['data']['event']); $this->assertNotEmpty($response['data']['payload']); $this->assertEquals($response['data']['payload']['name'], 'Chris Evans'); @@ -642,7 +642,7 @@ class RealtimeCustomClientTest extends Scope $this->assertContains('documents', $response['data']['channels']); $this->assertContains('collections.' . $data['actorsId'] . '.documents.' . $data['documentId'], $response['data']['channels']); $this->assertContains('collections.' . $data['actorsId'] . '.documents', $response['data']['channels']); - $this->assertEquals('database.documents.update', $response['data']['event']); + $this->assertEquals("collections.{$data['actorsId']}.documents.{$data['documentId']}.update", $response['data']['event']); $this->assertNotEmpty($response['data']['payload']); $this->assertEquals($response['data']['payload']['name'], 'Chris Evans 2'); @@ -680,7 +680,7 @@ class RealtimeCustomClientTest extends Scope $this->assertContains('documents', $response['data']['channels']); $this->assertContains('collections.' . $data['actorsId'] . '.documents.' . $document['body']['$id'], $response['data']['channels']); $this->assertContains('collections.' . $data['actorsId'] . '.documents', $response['data']['channels']); - $this->assertEquals('database.documents.delete', $response['data']['event']); + $this->assertEquals("collections.{$data['actorsId']}.documents.{$document['body']['$id']}.delete", $response['data']['event']); $this->assertNotEmpty($response['data']['payload']); $this->assertEquals($response['data']['payload']['name'], 'Bradley Cooper'); @@ -771,7 +771,7 @@ class RealtimeCustomClientTest extends Scope $this->assertContains('documents', $response['data']['channels']); $this->assertContains('collections.' . $data['actorsId'] . '.documents.' . $document['body']['$id'], $response['data']['channels']); $this->assertContains('collections.' . $actors['body']['$id'] . '.documents', $response['data']['channels']); - $this->assertEquals('database.documents.create', $response['data']['event']); + $this->assertEquals("collections.{$data['actorsId']}.documents.{$document['body']['$id']}.create", $response['data']['event']); $this->assertNotEmpty($response['data']['payload']); $this->assertEquals($response['data']['payload']['name'], 'Chris Evans'); @@ -802,7 +802,7 @@ class RealtimeCustomClientTest extends Scope $this->assertContains('documents', $response['data']['channels']); $this->assertContains('collections.' . $data['actorsId'] . '.documents.' . $data['documentId'], $response['data']['channels']); $this->assertContains('collections.' . $data['actorsId'] . '.documents', $response['data']['channels']); - $this->assertEquals('database.documents.update', $response['data']['event']); + $this->assertEquals("collections.{$data['actorsId']}.documents.{$document['body']['$id']}.update", $response['data']['event']); $this->assertNotEmpty($response['data']['payload']); $this->assertEquals($response['data']['payload']['name'], 'Chris Evans 2'); @@ -840,7 +840,7 @@ class RealtimeCustomClientTest extends Scope $this->assertContains('documents', $response['data']['channels']); $this->assertContains('collections.' . $data['actorsId'] . '.documents.' . $document['body']['$id'], $response['data']['channels']); $this->assertContains('collections.' . $data['actorsId'] . '.documents', $response['data']['channels']); - $this->assertEquals('database.documents.delete', $response['data']['event']); + $this->assertEquals("collections.{$data['actorsId']}.documents.{$document['body']['$id']}.delete", $response['data']['event']); $this->assertNotEmpty($response['data']['payload']); $this->assertEquals($response['data']['payload']['name'], 'Bradley Cooper'); @@ -908,7 +908,7 @@ class RealtimeCustomClientTest extends Scope $this->assertContains('files', $response['data']['channels']); $this->assertContains('buckets.' . $data['bucketId'] . '.files.' . $file['body']['$id'], $response['data']['channels']); $this->assertContains('buckets.' . $data['bucketId'] . '.files', $response['data']['channels']); - $this->assertEquals('storage.files.create', $response['data']['event']); + $this->assertEquals("buckets.{$data['bucketId']}.files.{$file['body']['$id']}.create", $response['data']['event']); $this->assertNotEmpty($response['data']['payload']); $data['fileId'] = $file['body']['$id']; @@ -935,7 +935,7 @@ class RealtimeCustomClientTest extends Scope $this->assertContains('files', $response['data']['channels']); $this->assertContains('buckets.' . $data['bucketId'] . '.files.' . $file['body']['$id'], $response['data']['channels']); $this->assertContains('buckets.' . $data['bucketId'] . '.files', $response['data']['channels']); - $this->assertEquals('storage.files.update', $response['data']['event']); + $this->assertEquals("buckets.{$data['bucketId']}.files.{$file['body']['$id']}.update", $response['data']['event']); $this->assertNotEmpty($response['data']['payload']); /** @@ -957,7 +957,7 @@ class RealtimeCustomClientTest extends Scope $this->assertContains('files', $response['data']['channels']); $this->assertContains('buckets.' . $data['bucketId'] . '.files.' . $file['body']['$id'], $response['data']['channels']); $this->assertContains('buckets.' . $data['bucketId'] . '.files', $response['data']['channels']); - $this->assertEquals('storage.files.delete', $response['data']['event']); + $this->assertEquals("buckets.{$data['bucketId']}.files.{$file['body']['$id']}.delete", $response['data']['event']); $this->assertNotEmpty($response['data']['payload']); $client->close(); @@ -1010,7 +1010,7 @@ class RealtimeCustomClientTest extends Scope $stdout= ''; $code = realpath(__DIR__ . '/../../../resources/functions'). "/$folder/code.tar.gz"; Console::execute('cd '.realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $stdout, $stderr); - + $deployment = $this->client->call(Client::METHOD_POST, '/functions/'.$functionId.'/deployments', array_merge([ 'content-type' => 'multipart/form-data', 'x-appwrite-project' => $this->getProject()['$id'], @@ -1058,7 +1058,7 @@ class RealtimeCustomClientTest extends Scope $this->assertContains('executions', $response['data']['channels']); $this->assertContains('executions.' . $execution['body']['$id'], $response['data']['channels']); $this->assertContains('functions.' . $execution['body']['functionId'], $response['data']['channels']); - $this->assertEquals('functions.executions.create', $response['data']['event']); + $this->assertEquals("functions.{$functionId}.executions.{$execution['body']['$id']}.create", $response['data']['event']); $this->assertNotEmpty($response['data']['payload']); $this->assertArrayHasKey('type', $responseUpdate); @@ -1071,7 +1071,7 @@ class RealtimeCustomClientTest extends Scope $this->assertContains('executions', $responseUpdate['data']['channels']); $this->assertContains('executions.' . $execution['body']['$id'], $responseUpdate['data']['channels']); $this->assertContains('functions.' . $execution['body']['functionId'], $responseUpdate['data']['channels']); - $this->assertEquals('functions.executions.update', $responseUpdate['data']['event']); + $this->assertEquals("functions.{$functionId}.executions.{$execution['body']['$id']}.update", $responseUpdate['data']['event']); $this->assertNotEmpty($responseUpdate['data']['payload']); $client->close(); @@ -1134,7 +1134,7 @@ class RealtimeCustomClientTest extends Scope $this->assertCount(2, $response['data']['channels']); $this->assertContains('teams', $response['data']['channels']); $this->assertContains('teams.' . $teamId, $response['data']['channels']); - $this->assertEquals('teams.create', $response['data']['event']); + $this->assertEquals("teams.{$teamId}.create", $response['data']['event']); $this->assertNotEmpty($response['data']['payload']); /** @@ -1160,7 +1160,7 @@ class RealtimeCustomClientTest extends Scope $this->assertCount(2, $response['data']['channels']); $this->assertContains('teams', $response['data']['channels']); $this->assertContains('teams.' . $teamId, $response['data']['channels']); - $this->assertEquals('teams.update', $response['data']['event']); + $this->assertEquals("teams.{$teamId}.update", $response['data']['event']); $this->assertNotEmpty($response['data']['payload']); $client->close(); @@ -1224,7 +1224,7 @@ class RealtimeCustomClientTest extends Scope $this->assertCount(2, $response['data']['channels']); $this->assertContains('memberships', $response['data']['channels']); $this->assertContains('memberships.' . $membershipId, $response['data']['channels']); - $this->assertEquals('teams.memberships.update', $response['data']['event']); + $this->assertEquals("teams.{$teamId}.memberships.{$membershipId}.update", $response['data']['event']); $this->assertNotEmpty($response['data']['payload']); $client->close(); From 8ec435ed1fd172836348e56211176028b0dbba29 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Sun, 17 Apr 2022 22:34:32 +0200 Subject: [PATCH 04/35] sync: changes --- app/controllers/api/database.php | 15 +- app/controllers/api/functions.php | 40 +++-- app/controllers/api/projects.php | 16 +- app/controllers/api/storage.php | 10 +- app/controllers/api/teams.php | 13 +- app/controllers/api/users.php | 9 +- app/controllers/shared/api.php | 13 +- app/workers/deletes.php | 18 +-- app/workers/functions.php | 152 +++++++++---------- composer.lock | 36 ++--- src/Appwrite/Event/Func.php | 109 +++++++++++++ tests/e2e/Services/Webhooks/WebhooksBase.php | 2 +- 12 files changed, 249 insertions(+), 184 deletions(-) create mode 100644 src/Appwrite/Event/Func.php diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 38e3399e82..a756c06533 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -674,6 +674,7 @@ App::delete('/v1/database/collections/:collectionId') /** @var Utopia\Database\Database $dbForProject */ /** @var Appwrite\Event\Event $events */ /** @var Appwrite\Event\Audit $audits */ + /** @var Appwrite\Event\Delete $deletes */ /** @var Appwrite\Stats\Stats $usage */ $collection = $dbForProject->getDocument('collections', $collectionId); @@ -689,10 +690,8 @@ App::delete('/v1/database/collections/:collectionId') $dbForProject->deleteCachedCollection('collection_' . $collection->getInternalId()); $deletes - ->setPayload([ - 'type' => DELETE_TYPE_DOCUMENT, - 'document' => $collection - ]) + ->setType(DELETE_TYPE_DOCUMENT) + ->setDocument($collection) ; $usage->setParam('database.collections.delete', 1); @@ -2134,7 +2133,7 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId') /** @var Utopia\Database\Database $dbForProject */ /** @var Appwrite\Event\Event $events */ /** @var Appwrite\Event\Audit $audits */ - /** @var Appwrite\Event\Event $deletes */ + /** @var Appwrite\Event\Delete $deletes */ /** @var Appwrite\Stats\Stats $usage */ /** @var string $mode */ @@ -2182,10 +2181,8 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId') $document->setAttribute('$collection', $collectionId); $deletes - ->setPayload([ - 'type' => DELETE_TYPE_AUDIT, - 'document' => $document - ]) + ->setType(DELETE_TYPE_AUDIT) + ->setDocument($document) ; $usage diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index ee19a737f6..bb1d0fa57f 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -3,6 +3,7 @@ use Ahc\Jwt\JWT; use Appwrite\Auth\Auth; use Appwrite\Event\Event; +use Appwrite\Event\Func; use Appwrite\Event\Validator\Event as ValidatorEvent; use Appwrite\Extend\Exception; use Appwrite\Utopia\Database\Validator\CustomId; @@ -442,7 +443,7 @@ App::delete('/v1/functions/:functionId') ->action(function ($functionId, $response, $dbForProject, $deletes, $events) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ - /** @var Appwrite\Event\Event $deletes */ + /** @var Appwrite\Event\Delete $deletes */ /** @var Appwrite\Event\Event $events */ $function = $dbForProject->getDocument('functions', $functionId); @@ -455,10 +456,9 @@ App::delete('/v1/functions/:functionId') throw new Exception('Failed to remove function from DB', 500, Exception::GENERAL_SERVER_ERROR); } - $deletes->setPayload([ - 'type' => DELETE_TYPE_DOCUMENT, - 'document' => $function - ]); + $deletes + ->setType(DELETE_TYPE_DOCUMENT) + ->setDocument($function); $events->setParam('functionId', $function->getId()); @@ -783,7 +783,7 @@ App::delete('/v1/functions/:functionId/deployments/:deploymentId') /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ /** @var Appwrite\Event\Event $usage */ - /** @var Appwrite\Event\Event $deletes */ + /** @var Appwrite\Event\Delete $deletes */ /** @var Appwrite\Event\Event $events */ /** @var Utopia\Storage\Device $deviceFunctions */ @@ -821,10 +821,8 @@ App::delete('/v1/functions/:functionId/deployments/:deploymentId') ->setParam('deploymentId', $deployment->getId()); $deletes - ->setPayload([ - 'type' => DELETE_TYPE_DOCUMENT, - 'document' => $deployment - ]); + ->setType(DELETE_TYPE_DOCUMENT) + ->setDocument($deployment); $response->noContent(); }); @@ -941,24 +939,20 @@ App::post('/v1/functions/:functionId/executions') $events ->setParam('functionId', $function->getId()) ->setParam('executionId', $execution->getId()) - ->setPayload([ - 'data' => $data, - 'jwt' => $jwt - ]) - ->setTrigger($execution); + ->setTrigger($function); if ($async) { - $event = new Event(Event::FUNCTIONS_QUEUE_NAME, Event::FUNCTIONS_CLASS_NAME); + $event = new Func(); $event + ->setType('http') + ->setExecution($execution) + ->setFunction($function) + ->setData($data) + ->setJWT($jwt) ->setProject($project) - ->setUser($user) - ->setTrigger($execution) - ->setPayload([ - 'data' => $data, - 'jwt' => $jwt - ]) - ->trigger(); + ->setUser($user); + $event->trigger(); $response->setStatusCode(Response::STATUS_CODE_CREATED); diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 3882170c4f..8d9ac5d0f0 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -547,7 +547,7 @@ App::delete('/v1/projects/:projectId') /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Document $user */ /** @var Utopia\Database\Database $dbForConsole */ - /** @var Appwrite\Event\Event $deletes */ + /** @var Appwrite\Event\Delete $deletes */ if (!Auth::passwordVerify($password, $user->getAttribute('password'))) { // Double check user password throw new Exception('Invalid credentials', 401, Exception::USER_INVALID_CREDENTIALS); @@ -560,10 +560,8 @@ App::delete('/v1/projects/:projectId') } $deletes - ->setPayload([ - 'type' => DELETE_TYPE_DOCUMENT, - 'document' => $project - ]) + ->setType(DELETE_TYPE_DOCUMENT) + ->setDocument($project) ; if (!$dbForConsole->deleteDocument('teams', $project->getAttribute('teamId', null))) { @@ -1422,6 +1420,7 @@ App::delete('/v1/projects/:projectId/domains/:domainId') ->action(function ($projectId, $domainId, $response, $dbForConsole, $deletes) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForConsole */ + /** @var Appwrite\Event\Delete $deletes */ $project = $dbForConsole->getDocument('projects', $projectId); @@ -1443,11 +1442,8 @@ App::delete('/v1/projects/:projectId/domains/:domainId') $dbForConsole->deleteCachedDocument('projects', $project->getId()); $deletes - ->setPayload([ - 'type' => DELETE_TYPE_CERTIFICATES, - 'document' => $domain - ]) - ; + ->setType(DELETE_TYPE_CERTIFICATES) + ->setDocument($domain); $response->noContent(); }); diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 902b44d861..e538ba6a3d 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -312,7 +312,7 @@ App::delete('/v1/storage/buckets/:bucketId') /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ /** @var Appwrite\Event\Audit $audits */ - /** @var Appwrite\Event\Event $deletes */ + /** @var Appwrite\Event\Delete $deletes */ /** @var Appwrite\Event\Event $events */ /** @var Appwrite\Stats\Stats $usage */ @@ -327,12 +327,8 @@ App::delete('/v1/storage/buckets/:bucketId') } $deletes - ->setParam('bucketId', $bucket->getId()) - ->setPayload([ - 'type' => DELETE_TYPE_DOCUMENT, - 'document' => $bucket - ]) - ; + ->setType(DELETE_TYPE_DOCUMENT) + ->setDocument($bucket); $events ->setParam('bucketId', $bucket->getId()) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index efbd2c3e50..6c34649fd9 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -226,7 +226,7 @@ App::delete('/v1/teams/:teamId') /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ /** @var Appwrite\Event\Event $events */ - /** @var Appwrite\Event\Event $deletes */ + /** @var Appwrite\Event\Delete $deletes */ $team = $dbForProject->getDocument('teams', $teamId); @@ -250,13 +250,8 @@ App::delete('/v1/teams/:teamId') } $deletes - ->setParam('teamId', $team->getId()) - ->setPayload([ - 'type' => DELETE_TYPE_DOCUMENT, - 'document' => $team - ]) - - ; + ->setType(DELETE_TYPE_DOCUMENT) + ->setDocument($team); $events ->setParam('teamId', $team->getId()) @@ -834,7 +829,7 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId') $memberships = $user->getAttribute('memberships', []); - foreach ($memberships as $key => $child) { + foreach ($memberships as $key => $child) { /** @var Document $child */ if ($membershipId == $child->getId()) { diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 32d56f2ee1..23e725440e 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -760,7 +760,7 @@ App::delete('/v1/users/:userId') /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ /** @var Appwrite\Event\Event $events */ - /** @var Appwrite\Event\Event $deletes */ + /** @var Appwrite\Event\Delete $deletes */ /** @var Appwrite\Stats\Stats $usage */ $user = $dbForProject->getDocument('users', $userId); @@ -789,11 +789,8 @@ App::delete('/v1/users/:userId') $dbForProject->updateDocument('users', $userId, $user); $deletes - ->setParam('userId', $userId) - ->setPayload([ - 'type' => DELETE_TYPE_DOCUMENT, - 'document' => $clone - ]) + ->setType(DELETE_TYPE_DOCUMENT) + ->setDocument($clone) ; $events diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index bec53e6f98..5c15c4851f 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -187,7 +187,7 @@ App::shutdown(function ($utopia, $request, $response, $project, $events, $audits /** @var Appwrite\Event\Event $events */ /** @var Appwrite\Event\Audit $audits */ /** @var Appwrite\Stats\Stats $usage */ - /** @var Appwrite\Event\Event $deletes */ + /** @var Appwrite\Event\Delete $deletes */ /** @var Appwrite\Event\Database $database */ /** @var bool $mode */ /** @var Utopia\Database\Database $dbForProject */ @@ -251,7 +251,7 @@ App::shutdown(function ($utopia, $request, $response, $project, $events, $audits $audits->trigger(); } - if (!empty($deletes->getPayload())) { + if (!empty($deletes->getType())) { $deletes->trigger(); } @@ -265,11 +265,10 @@ App::shutdown(function ($utopia, $request, $response, $project, $events, $audits && $mode !== APP_MODE_ADMIN // TODO: add check to make sure user is admin && !empty($route->getLabel('sdk.namespace', null))) { // Don't calculate console usage on admin mode - // $usage - // ->setParam('networkRequestSize', $request->getSize() + $usage->getParam('storage')) - // ->setParam('networkResponseSize', $response->getSize()) - // ->submit() - // ; + $usage + ->setParam('networkRequestSize', $request->getSize() + $usage->getParam('storage')) + ->setParam('networkResponseSize', $response->getSize()) + ->submit(); } }, ['utopia', 'request', 'response', 'project', 'events', 'audits', 'usage', 'deletes', 'database', 'mode', 'dbForProject'], 'api'); \ No newline at end of file diff --git a/app/workers/deletes.php b/app/workers/deletes.php index 9418d8b642..779dfbb2c8 100644 --- a/app/workers/deletes.php +++ b/app/workers/deletes.php @@ -42,13 +42,11 @@ class DeletesV1 extends Worker public function run(): void { $project = new Document($this->args['project'] ?? []); - $payload = $this->args['payload'] ?? []; - - $type = $payload['type'] ?? ''; + $type = $this->args['type'] ?? ''; switch (strval($type)) { case DELETE_TYPE_DOCUMENT: - $document = new Document($payload['document'] ?? []); + $document = new Document($this->args['document'] ?? []); switch ($document->getCollection()) { case DELETE_TYPE_COLLECTIONS: @@ -79,7 +77,7 @@ class DeletesV1 extends Worker break; case DELETE_TYPE_EXECUTIONS: - $this->deleteExecutionLogs($payload['timestamp']); + $this->deleteExecutionLogs($this->args['timestamp']); break; case DELETE_TYPE_AUDIT: @@ -87,7 +85,7 @@ class DeletesV1 extends Worker $document = new Document($payload['document'] ?? []); if (!empty($timestamp)) { - $this->deleteAuditLogs($payload['timestamp']); + $this->deleteAuditLogs($this->args['timestamp']); } if (!$document->isEmpty()) { @@ -97,20 +95,20 @@ class DeletesV1 extends Worker break; case DELETE_TYPE_ABUSE: - $this->deleteAbuseLogs($payload['timestamp']); + $this->deleteAbuseLogs($this->args['timestamp']); break; case DELETE_TYPE_REALTIME: - $this->deleteRealtimeUsage($payload['timestamp']); + $this->deleteRealtimeUsage($this->args['timestamp']); break; case DELETE_TYPE_CERTIFICATES: - $document = new Document($payload['document']); + $document = new Document($this->args['document']); $this->deleteCertificates($document); break; case DELETE_TYPE_USAGE: - $this->deleteUsageStats($payload['timestamp1d'], $payload['timestamp30m']); + $this->deleteUsageStats($this->args['timestamp1d'], $this->args['timestamp30m']); break; default: Console::error('No delete operation for type: ' . $type); diff --git a/app/workers/functions.php b/app/workers/functions.php index 94f608d464..5b13aef1ad 100644 --- a/app/workers/functions.php +++ b/app/workers/functions.php @@ -1,6 +1,7 @@ args['events']; - $payload = $this->args['payload']; - $user = new Document($this->args['user'] ?? []); + $type = $this->args['type'] ?? ''; + $events = $this->args['events'] ?? []; $project = new Document($this->args['project'] ?? []); - $execution = new Document($this->args['trigger'] ?? []); - - $event = $events[0] ?? ''; - $data = $payload['data'] ?? ''; - $jwt = $payload['jwt'] ?? ''; - $webhooks = $project->getAttribute('webhooks', []); - $trigger = $execution->getAttribute('trigger', ''); - $scheduleOriginal = $execution->getAttribute('scheduleOriginal', ''); - $eventData = !empty($execution->getAttribute('eventData')) ? json_encode($execution->getAttribute('eventData')) : ''; - $jwt = $execution->getAttribute('jwt', ''); + $user = new Document($this->args['user'] ?? []); + $payload = json_encode($this->args['payload'] ?? []); $database = $this->getProjectDB($project->getId()); - switch ($trigger) { - case 'event': - $limit = 30; - $sum = 30; - $offset = 0; - $functions = []; - /** @var Document[] $functions */ + /** + * Handle Event execution. + */ + if (!empty($events)) { + $limit = 30; + $sum = 30; + $offset = 0; + $functions = []; + /** @var Document[] $functions */ - while ($sum >= $limit) { - $functions = Authorization::skip(fn() => $database->find('functions', [], $limit, $offset, ['name'], [Database::ORDER_ASC])); - $sum = \count($functions); - $offset = $offset + $limit; + while ($sum >= $limit) { + $functions = Authorization::skip(fn () => $database->find('functions', [], $limit, $offset, ['name'], [Database::ORDER_ASC])); + $sum = \count($functions); + $offset = $offset + $limit; - Console::log('Fetched ' . $sum . ' functions...'); + Console::log('Fetched ' . $sum . ' functions...'); - foreach ($functions as $function) { - if (!array_intersect($events, $function->getAttribute('events', []))) { - continue; - } - - Console::success('Iterating function: ' . $function->getAttribute('name')); - - $this->execute( - projectId: $project->getId(), - function: $function, - dbForProject: $database, - executionId: $execution->getId(), - webhooks: $webhooks, - trigger: $trigger, - event: $event, - eventData: $eventData, - data: $data, - userId: $user->getId(), - jwt: $jwt - ); - - Console::success('Triggered function: ' . $event); + foreach ($functions as $function) { + if (!array_intersect($events, $function->getAttribute('events', []))) { + continue; } - } - break; + Console::success('Iterating function: ' . $function->getAttribute('name')); + + $this->execute( + projectId: $project->getId(), + function: $function, + dbForProject: $database, + trigger: 'event', + event: $events[0], + eventData: $payload, + userId: $user->getId() + ); + + Console::success('Triggered function: ' . $events[0]); + } + } + + return; + } + + /** + * Handle Schedule and HTTP execution. + */ + $user = new Document($this->args['user'] ?? []); + $project = new Document($this->args['project'] ?? []); + $execution = new Document($this->args['execution'] ?? []); + + switch ($type) { case 'http': - $function = Authorization::skip(fn() => $database->getDocument('functions', $execution->getAttribute('functionId'))); + $jwt = $this->args['jwt'] ?? ''; + $data = $this->args['data'] ?? ''; + + $function = Authorization::skip(fn () => $database->getDocument('functions', $execution->getAttribute('functionId'))); $this->execute( projectId: $project->getId(), function: $function, dbForProject: $database, executionId: $execution->getId(), - webhooks: $webhooks, - trigger: $trigger, - event: '', - eventData: '', + trigger: 'http', data: $data, userId: $user->getId(), jwt: $jwt @@ -115,7 +115,8 @@ class FunctionsV1 extends Worker break; case 'schedule': - $function = Authorization::skip(fn() => $database->getDocument('functions', $execution->getAttribute('functionId'))); + $scheduleOriginal = $execution->getAttribute('scheduleOriginal', ''); + $function = Authorization::skip(fn () => $database->getDocument('functions', $execution->getAttribute('functionId'))); /* * 1. Get Original Task @@ -158,28 +159,21 @@ class FunctionsV1 extends Worker throw new Exception('Function update failed.'); } - ResqueScheduler::enqueueAt($next, Event::FUNCTIONS_QUEUE_NAME, Event::FUNCTIONS_CLASS_NAME, [ - 'projectId' => $project->getId(), - 'webhooks' => $webhooks, - 'functionId' => $function->getId(), - 'userId' => $user->getId(), - 'executionId' => null, - 'trigger' => 'schedule', - 'scheduleOriginal' => $function->getAttribute('schedule', ''), - ]); // Async task reschedule + $reschedule = new Func(); + $reschedule + ->setFunction($function) + ->setType('schedule') + ->setUser($user) + ->setProject($project); + + // Async task reschedule + $reschedule->schedule($next); $this->execute( projectId: $project->getId(), function: $function, dbForProject: $database, - executionId: $execution->getId(), - webhooks: $webhooks, - trigger: $trigger, - event: $event, - eventData: $eventData, - data: $data, - userId: $user->getId(), - jwt: $jwt + trigger: 'schedule' ); break; @@ -190,14 +184,13 @@ class FunctionsV1 extends Worker string $projectId, Document $function, Database $dbForProject, - string $executionId, - array $webhooks, string $trigger, - string $event, - string $eventData, - string $data, - string $userId, - string $jwt + string $executionId = null, + string $event = null, + string $eventData = null, + string $data = null, + string $userId = null, + string $jwt = null ) { $functionId = $function->getId(); @@ -318,7 +311,6 @@ class FunctionsV1 extends Worker $executionUpdate ->setParam('projectId', $projectId) ->setParam('userId', $userId) - ->setParam('webhooks', $webhooks) ->setParam('event', 'functions.executions.update') ->setParam('eventData', $execution->getArrayCopy(array_keys($executionModel->getRules()))) ->trigger(); diff --git a/composer.lock b/composer.lock index d9d2f30b2b..29925e64ed 100644 --- a/composer.lock +++ b/composer.lock @@ -2250,16 +2250,16 @@ }, { "name": "utopia-php/framework", - "version": "0.19.8", + "version": "0.19.9", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "8c3b3e330546fd6cd65bd1f8d8d08882ff3abb7d" + "reference": "4af9fc866edce1b8cff94731fb26c27599118e87" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/8c3b3e330546fd6cd65bd1f8d8d08882ff3abb7d", - "reference": "8c3b3e330546fd6cd65bd1f8d8d08882ff3abb7d", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/4af9fc866edce1b8cff94731fb26c27599118e87", + "reference": "4af9fc866edce1b8cff94731fb26c27599118e87", "shasum": "" }, "require": { @@ -2293,9 +2293,9 @@ ], "support": { "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.19.8" + "source": "https://github.com/utopia-php/framework/tree/0.19.9" }, - "time": "2022-04-12T00:28:15+00:00" + "time": "2022-04-14T15:39:47+00:00" }, { "name": "utopia-php/image", @@ -3551,16 +3551,16 @@ }, { "name": "matthiasmullie/minify", - "version": "1.3.66", + "version": "1.3.67", "source": { "type": "git", "url": "https://github.com/matthiasmullie/minify.git", - "reference": "45fd3b0f1dfa2c965857c6d4a470bea52adc31a6" + "reference": "acaee1b7ca3cd67a39d7f98673cacd7e4739a8d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/matthiasmullie/minify/zipball/45fd3b0f1dfa2c965857c6d4a470bea52adc31a6", - "reference": "45fd3b0f1dfa2c965857c6d4a470bea52adc31a6", + "url": "https://api.github.com/repos/matthiasmullie/minify/zipball/acaee1b7ca3cd67a39d7f98673cacd7e4739a8d9", + "reference": "acaee1b7ca3cd67a39d7f98673cacd7e4739a8d9", "shasum": "" }, "require": { @@ -3609,23 +3609,15 @@ ], "support": { "issues": "https://github.com/matthiasmullie/minify/issues", - "source": "https://github.com/matthiasmullie/minify/tree/1.3.66" + "source": "https://github.com/matthiasmullie/minify/tree/1.3.67" }, "funding": [ { - "url": "https://github.com/[user1", - "type": "github" - }, - { - "url": "https://github.com/matthiasmullie] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g.", - "type": "github" - }, - { - "url": "https://github.com/user2", + "url": "https://github.com/matthiasmullie", "type": "github" } ], - "time": "2021-01-06T15:18:10+00:00" + "time": "2022-03-24T08:54:59+00:00" }, { "name": "matthiasmullie/path-converter", @@ -6584,5 +6576,5 @@ "platform-overrides": { "php": "8.0" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.1.0" } diff --git a/src/Appwrite/Event/Func.php b/src/Appwrite/Event/Func.php new file mode 100644 index 0000000000..3caefdd6f2 --- /dev/null +++ b/src/Appwrite/Event/Func.php @@ -0,0 +1,109 @@ +function = $function; + + return $this; + } + + public function getFunction(): ?Document + { + return $this->function; + } + + public function setExecution(Document $execution): self + { + $this->execution = $execution; + + return $this; + } + + public function getExecution(): ?Document + { + return $this->execution; + } + + public function setType(string $type): self + { + $this->type = $type; + + return $this; + } + + public function getType(): string + { + return $this->type; + } + + public function setData(string $data): self + { + $this->data = $data; + + return $this; + } + + public function getData(): string + { + return $this->data; + } + + public function setJWT(string $jwt): self + { + $this->jwt = $jwt; + + return $this; + } + + public function getJWT(): string + { + return $this->jwt; + } + + public function trigger(): string|bool + { + return Resque::enqueue($this->queue, $this->class, [ + 'project' => $this->project, + 'user' => $this->user, + 'function' => $this->function, + 'execution' => $this->execution, + 'type' => $this->type, + 'jwt' => $this->jwt, + 'payload' => $this->payload, + 'data' => $this->data + ]); + } + + public function schedule(DateTime|int $at): void + { + ResqueScheduler::enqueueAt($at, $this->queue, $this->class, [ + 'project' => $this->project, + 'user' => $this->user, + 'function' => $this->function, + 'execution' => $this->execution, + 'type' => $this->type, + 'payload' => $this->payload, + 'data' => $this->data + ]); + } +} \ No newline at end of file diff --git a/tests/e2e/Services/Webhooks/WebhooksBase.php b/tests/e2e/Services/Webhooks/WebhooksBase.php index b8ed3b7707..9ddc1119ac 100644 --- a/tests/e2e/Services/Webhooks/WebhooksBase.php +++ b/tests/e2e/Services/Webhooks/WebhooksBase.php @@ -23,7 +23,7 @@ trait WebhooksBase 'write' => ['role:all'], 'permission' => 'document', ]); - + $this->assertEquals($actors['headers']['status-code'], 201); $this->assertNotEmpty($actors['body']['$id']); From 7790d72c0c91b14437f7b9b33f998c995ab4527a Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Mon, 18 Apr 2022 18:21:45 +0200 Subject: [PATCH 05/35] fix: realtime, tests and webhooks --- app/config/events.php | 8 +- app/controllers/api/account.php | 29 +- app/controllers/api/database.php | 14 +- app/controllers/api/functions.php | 2 +- app/controllers/api/storage.php | 6 +- app/controllers/shared/api.php | 30 +- app/controllers/web/console.php | 2 +- app/workers/builds.php | 33 +- app/workers/database.php | 40 +- app/workers/functions.php | 55 ++- app/workers/webhooks.php | 7 +- src/Appwrite/Event/Audit.php | 51 ++- src/Appwrite/Event/Certificate.php | 43 +- src/Appwrite/Event/Database.php | 41 +- src/Appwrite/Event/Delete.php | 32 +- src/Appwrite/Event/Event.php | 34 +- src/Appwrite/Event/Func.php | 75 +++- src/Appwrite/Event/Mail.php | 77 +++- src/Appwrite/Event/Validator/Event.php | 11 +- src/Appwrite/Messaging/Adapter.php | 2 +- src/Appwrite/Messaging/Adapter/Realtime.php | 8 +- .../Projects/ProjectsConsoleClientTest.php | 350 ++++++++-------- .../Realtime/RealtimeConsoleClientTest.php | 111 +++-- .../Realtime/RealtimeCustomClientTest.php | 396 +++++++++++++----- tests/e2e/Services/Webhooks/WebhooksBase.php | 291 +++++++++---- .../Webhooks/WebhooksCustomClientTest.php | 199 +++++++-- .../Webhooks/WebhooksCustomServerTest.php | 218 +++++++--- tests/e2e/Services/Workers/WebhooksTest.php | 154 ------- tests/unit/Event/EventTest.php | 9 +- tests/unit/Messaging/MessagingTest.php | 51 ++- 30 files changed, 1620 insertions(+), 759 deletions(-) delete mode 100644 tests/e2e/Services/Workers/WebhooksTest.php diff --git a/app/config/events.php b/app/config/events.php index 157e312888..9a5fecfbb0 100644 --- a/app/config/events.php +++ b/app/config/events.php @@ -281,11 +281,5 @@ return [ 'description' => 'This event triggers when a team memberships is deleted.', 'model' => Response::MODEL_MEMBERSHIP, 'note' => 'version >= 0.7', - ], - - 'users.*' => [ - 'description' => 'This event triggers when a team memberships is deleted.', - 'model' => Response::MODEL_MEMBERSHIP, - 'note' => 'version >= 0.7', - ], + ] ]; diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 1907c9bb8e..61a14bf571 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -734,16 +734,13 @@ App::post('/v1/account/sessions/magic-url') ->trigger() ; - $events - ->setParam('eventData', - $response->output($token->setAttribute('secret', $loginSecret), + $events->setParam('eventData', $response->output( + $token->setAttribute('secret', $loginSecret), Response::MODEL_TOKEN - )) - ; + )); - $token // Hide secret for clients - ->setAttribute('secret', - ($isPrivilegedUser || $isAppUser) ? $loginSecret : ''); + // Hide secret for clients + $token->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? $loginSecret : ''); $audits ->setResource('user/'.$user->getId()) @@ -1907,21 +1904,18 @@ App::post('/v1/account/recovery') $events ->setParam('userId', $profile->getId()) ->setParam('tokenId', $recovery->getId()) + ->setUser($profile) ->setPayload($response->output( $recovery->setAttribute('secret', $secret), Response::MODEL_TOKEN )) ; - $recovery // Hide secret for clients, sp - ->setAttribute('secret', - ($isPrivilegedUser || $isAppUser) ? $secret : ''); + // Hide secret for clients + $recovery->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? $secret : ''); $audits->setResource('user/' . $profile->getId()); - - $usage - ->setParam('users.update', 1) - ; + $usage->setParam('users.update', 1); $response->setStatusCode(Response::STATUS_CODE_CREATED); $response->dynamic($recovery, Response::MODEL_TOKEN); @@ -2094,9 +2088,8 @@ App::post('/v1/account/verification') )) ; - $verification // Hide secret for clients, sp - ->setAttribute('secret', - ($isPrivilegedUser || $isAppUser) ? $verificationSecret : ''); + // Hide secret for clients + $verification->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? $verificationSecret : ''); $audits->setResource('user/' . $user->getId()); $usage->setParam('users.update', 1); diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index a756c06533..a1bff4199c 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -124,7 +124,7 @@ function createAttribute(string $collectionId, Document $attribute, Response $re ; $events - ->setTrigger($collection) + ->setContext($collection) ->setParam('collectionId', $collection->getId()) ->setParam('attributeId', $attribute->getId()) ; @@ -1315,7 +1315,7 @@ App::delete('/v1/database/collections/:collectionId/attributes/:key') $events ->setParam('collectionId', $collection->getId()) ->setParam('attributeId', $attribute->getId()) - ->setTrigger($collection) + ->setContext($collection) ->setPayload($response->output($attribute, $model)) ; @@ -1429,7 +1429,7 @@ App::post('/v1/database/collections/:collectionId/indexes') $events ->setParam('collectionId', $collection->getId()) ->setParam('indexId', $index->getId()) - ->setTrigger($collection) + ->setContext($collection) ; $audits @@ -1577,7 +1577,7 @@ App::delete('/v1/database/collections/:collectionId/indexes/:key') $events ->setParam('collectionId', $collection->getId()) ->setParam('indexId', $index->getId()) - ->setTrigger($collection) + ->setContext($collection) ->setPayload($response->output($index, Response::MODEL_INDEX)) ; @@ -1694,7 +1694,7 @@ App::post('/v1/database/collections/:collectionId/documents') $events ->setParam('collectionId', $collection->getId()) ->setParam('documentId', $document->getId()) - ->setTrigger($collection) + ->setContext($collection) ; $usage @@ -2092,7 +2092,7 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId') $events ->setParam('collectionId', $collection->getId()) ->setParam('documentId', $document->getId()) - ->setTrigger($collection) + ->setContext($collection) ; $usage @@ -2193,7 +2193,7 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId') $events ->setParam('collectionId', $collection->getId()) ->setParam('documentId', $document->getId()) - ->setTrigger($collection) + ->setContext($collection) ->setPayload($response->output($document, Response::MODEL_DOCUMENT)) ; diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index bb1d0fa57f..b8038676ba 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -939,7 +939,7 @@ App::post('/v1/functions/:functionId/executions') $events ->setParam('functionId', $function->getId()) ->setParam('executionId', $execution->getId()) - ->setTrigger($function); + ->setContext($function); if ($async) { $event = new Func(); diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index e538ba6a3d..12cc35e17b 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -682,7 +682,7 @@ App::post('/v1/storage/buckets/:bucketId/files') $events ->setParam('bucketId', $bucket->getId()) ->setParam('fileId', $file->getId()) - ->setTrigger($bucket) + ->setContext($bucket) ; $metadata = null; // was causing leaks as it was passed by reference @@ -1413,7 +1413,7 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId') $events ->setParam('bucketId', $bucket->getId()) ->setParam('fileId', $file->getId()) - ->setTrigger($bucket) + ->setContext($bucket) ; $audits->setResource('file/' . $file->getId()); @@ -1522,7 +1522,7 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId') $events ->setParam('bucketId', $bucket->getId()) ->setParam('fileId', $file->getId()) - ->setTrigger($bucket) + ->setContext($bucket) ->setPayload($response->output($file, Response::MODEL_FILE)) ; diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 5c15c4851f..c92adaed09 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -7,14 +7,8 @@ use Utopia\App; use Appwrite\Extend\Exception; use Utopia\Abuse\Abuse; use Utopia\Abuse\Adapters\TimeLimit; -use Utopia\Database\Database; use Utopia\Database\Document; -use Utopia\Database\Query; -use Utopia\Storage\Device\DOSpaces; use Utopia\Database\Validator\Authorization; -use Utopia\Storage\Device\Local; -use Utopia\Storage\Device\S3; -use Utopia\Storage\Storage; App::init(function ($utopia, $request, $response, $project, $user, $events, $audits, $mails, $usage, $deletes, $database, $dbForProject, $mode) { /** @var Utopia\App $utopia */ @@ -193,13 +187,16 @@ App::shutdown(function ($utopia, $request, $response, $project, $events, $audits /** @var Utopia\Database\Database $dbForProject */ if (!empty($events->getEvent())) { + if (empty($events->getPayload())) { + $events->setPayload($response->getPayload()); + } /** * Trigger functions. */ $events ->setClass(Event::FUNCTIONS_CLASS_NAME) ->setQueue(Event::FUNCTIONS_QUEUE_NAME) - ->setPayload($response->getPayload()) + ->setPayload($events->getPayload()) ->trigger(); /** @@ -208,16 +205,17 @@ App::shutdown(function ($utopia, $request, $response, $project, $events, $audits $events ->setClass(Event::WEBHOOK_CLASS_NAME) ->setQueue(Event::WEBHOOK_QUEUE_NAME) - ->setPayload($response->getPayload()) + ->setPayload($events->getPayload()) ->trigger(); + var_dump($events->getEvent()); /** * Trigger realtime. */ if ($project->getId() !== 'console') { $allEvents = Event::generateEvents($events->getEvent(), $events->getParams()); - $payload = new Document($response->getPayload()); - $trigger = $events->getTrigger() ?? false; + $payload = new Document($events->getPayload()); + $trigger = $events->getContext() ?? false; $collection = ($trigger && $trigger->getCollection() === 'collections') ? $trigger : null; $bucket = ($trigger && $trigger->getCollection() === 'buckets') ? $trigger : null; @@ -231,12 +229,12 @@ App::shutdown(function ($utopia, $request, $response, $project, $events, $audits ); Realtime::send( - $target['projectId'] ?? $project->getId(), - $response->getPayload(), - $allEvents[0], - $target['channels'], - $target['roles'], - [ + projectId: $target['projectId'] ?? $project->getId(), + payload: $events->getPayload(), + events: $allEvents, + channels: $target['channels'], + roles: $target['roles'], + options: [ 'permissionsChanged' => $target['permissionsChanged'], 'userId' => $events->getParam('userId') ] diff --git a/app/controllers/web/console.php b/app/controllers/web/console.php index 12cb625a8e..58bc905dbe 100644 --- a/app/controllers/web/console.php +++ b/app/controllers/web/console.php @@ -165,7 +165,7 @@ App::get('/console/webhooks') $page ->setParam('events', Config::getParam('events', [])) ; - + $layout ->setParam('title', APP_NAME.' - Webhooks') ->setParam('body', $page); diff --git a/app/workers/builds.php b/app/workers/builds.php index 91b93699b8..83197bdfa2 100644 --- a/app/workers/builds.php +++ b/app/workers/builds.php @@ -1,7 +1,9 @@ setAttribute('status', 'building'); $build = $dbForProject->updateDocument('builds', $buildId, $build); - /** Send realtime event */ - $target = Realtime::fromPayload('functions.deployments.update', $build, $project); + /** Trigger Webhook */ + $deploymentModel = new Deployment(); + $deploymentUpdate = new Event(Event::WEBHOOK_QUEUE_NAME, Event::WEBHOOK_CLASS_NAME); + $deploymentUpdate + ->setProject($project) + ->setEvent('functions.[functionId].deployments.[deploymentId].update') + ->setParam('functionId', $function->getId()) + ->setParam('deploymentId', $deployment->getId()) + ->setPayload($deployment->getArrayCopy(array_keys($deploymentModel->getRules()))) + ->trigger(); + + /** Trigger Functions */ + $deploymentUpdate + ->setClass(Event::FUNCTIONS_CLASS_NAME) + ->setQueue(Event::FUNCTIONS_QUEUE_NAME) + ->trigger(); + + /** Trigger Realtime */ + $allEvents = Event::generateEvents('functions.[functionId].deployments.[deploymentId].update', [ + 'functionId' => $function->getId(), + 'deploymentId' => $deployment->getId() + ]); + $target = Realtime::fromPayload($allEvents[0], $build, $project); Realtime::send( projectId: 'console', payload: $build->getArrayCopy(), - event: 'functions.deployments.update', + events: $allEvents, channels: $target['channels'], roles: $target['roles'] ); @@ -179,11 +202,11 @@ class BuildsV1 extends Worker /** * Send realtime Event */ - $target = Realtime::fromPayload('functions.deployments.update', $build, $project); + $target = Realtime::fromPayload($allEvents[0], $build, $project); Realtime::send( projectId: 'console', payload: $build->getArrayCopy(), - event: 'functions.deployments.update', + events: $allEvents, channels: $target['channels'], roles: $target['roles'] ); diff --git a/app/workers/database.php b/app/workers/database.php index f9bdb04e4e..bcb98e6220 100644 --- a/app/workers/database.php +++ b/app/workers/database.php @@ -1,5 +1,6 @@ getConsoleDB(); $dbForProject = $this->getProjectDB($projectId); - $event = 'database.attributes.update'; + $events = Event::generateEvents('collections.[collectionId].attributes.[attributeId].update', [ + 'collectionId' => $collection->getId(), + 'attributeId' => $attribute->getId() + ]); $collectionId = $collection->getId(); $key = $attribute->getAttribute('key', ''); $type = $attribute->getAttribute('type', ''); @@ -93,12 +97,12 @@ class DatabaseV1 extends Worker Console::error($th->getMessage()); $dbForProject->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'failed')); } finally { - $target = Realtime::fromPayload($event, $attribute, $project); + $target = Realtime::fromPayload($events[0], $attribute, $project); Realtime::send( projectId: 'console', payload: $attribute->getArrayCopy(), - event: $event, + events: $events, channels: $target['channels'], roles: $target['roles'], options: [ @@ -121,7 +125,10 @@ class DatabaseV1 extends Worker $dbForConsole = $this->getConsoleDB(); $dbForProject = $this->getProjectDB($projectId); - $event = 'database.attributes.delete'; + $events = Event::generateEvents('collections.[collectionId].attributes.[attributeId].delete', [ + 'collectionId' => $collection->getId(), + 'attributeId' => $attribute->getId() + ]); $collectionId = $collection->getId(); $key = $attribute->getAttribute('key', ''); $status = $attribute->getAttribute('status', ''); @@ -142,12 +149,12 @@ class DatabaseV1 extends Worker Console::error($th->getMessage()); $dbForProject->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'stuck')); } finally { - $target = Realtime::fromPayload($event, $attribute, $project); + $target = Realtime::fromPayload($events[0], $attribute, $project); Realtime::send( projectId: 'console', payload: $attribute->getArrayCopy(), - event: $event, + events: $events, channels: $target['channels'], roles: $target['roles'], options: [ @@ -222,7 +229,10 @@ class DatabaseV1 extends Worker $dbForConsole = $this->getConsoleDB(); $dbForProject = $this->getProjectDB($projectId); - $event = 'database.indexes.update'; + $events = Event::generateEvents('collections.[collectionId].indexes.[indexId].update', [ + 'collectionId' => $collection->getId(), + 'indexId' => $index->getId() + ]); $collectionId = $collection->getId(); $key = $index->getAttribute('key', ''); $type = $index->getAttribute('type', ''); @@ -240,12 +250,12 @@ class DatabaseV1 extends Worker Console::error($th->getMessage()); $dbForProject->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'failed')); } finally { - $target = Realtime::fromPayload($event, $index, $project); + $target = Realtime::fromPayload($events[0], $index, $project); Realtime::send( projectId: 'console', payload: $index->getArrayCopy(), - event: $event, + events: $events, channels: $target['channels'], roles: $target['roles'], options: [ @@ -268,10 +278,12 @@ class DatabaseV1 extends Worker $dbForConsole = $this->getConsoleDB(); $dbForProject = $this->getProjectDB($projectId); - $collectionId = $collection->getId(); + $events = Event::generateEvents('collections.[collectionId].indexes.[indexId].delete', [ + 'collectionId' => $collection->getId(), + 'indexId' => $index->getId() + ]); $key = $index->getAttribute('key'); $status = $index->getAttribute('status', ''); - $event = 'database.indexes.delete'; $project = $dbForConsole->getDocument('projects', $projectId); try { @@ -283,12 +295,12 @@ class DatabaseV1 extends Worker Console::error($th->getMessage()); $dbForProject->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'stuck')); } finally { - $target = Realtime::fromPayload($event, $index, $project); + $target = Realtime::fromPayload($events[0], $index, $project); Realtime::send( projectId: 'console', payload: $index->getArrayCopy(), - event: $event, + events: $events, channels: $target['channels'], roles: $target['roles'], options: [ @@ -298,6 +310,6 @@ class DatabaseV1 extends Worker ); } - $dbForProject->deleteCachedDocument('collections', $collectionId); + $dbForProject->deleteCachedDocument('collections', $collection->getId()); } } diff --git a/app/workers/functions.php b/app/workers/functions.php index 5b13aef1ad..c70daf289d 100644 --- a/app/workers/functions.php +++ b/app/workers/functions.php @@ -71,13 +71,13 @@ class FunctionsV1 extends Worker Console::success('Iterating function: ' . $function->getAttribute('name')); $this->execute( - projectId: $project->getId(), + project: $project, function: $function, dbForProject: $database, trigger: 'event', event: $events[0], eventData: $payload, - userId: $user->getId() + user: $user ); Console::success('Triggered function: ' . $events[0]); @@ -102,13 +102,13 @@ class FunctionsV1 extends Worker $function = Authorization::skip(fn () => $database->getDocument('functions', $execution->getAttribute('functionId'))); $this->execute( - projectId: $project->getId(), + project: $project, function: $function, dbForProject: $database, executionId: $execution->getId(), trigger: 'http', data: $data, - userId: $user->getId(), + user: $user, jwt: $jwt ); @@ -170,7 +170,7 @@ class FunctionsV1 extends Worker $reschedule->schedule($next); $this->execute( - projectId: $project->getId(), + project: $project, function: $function, dbForProject: $database, trigger: 'schedule' @@ -181,7 +181,7 @@ class FunctionsV1 extends Worker } private function execute( - string $projectId, + Document $project, Document $function, Database $dbForProject, string $trigger, @@ -189,7 +189,7 @@ class FunctionsV1 extends Worker string $event = null, string $eventData = null, string $data = null, - string $userId = null, + ?Document $user = null, string $jwt = null ) { @@ -227,13 +227,13 @@ class FunctionsV1 extends Worker $runtime = $runtimes[$function->getAttribute('runtime')]; /** Create execution or update execution status */ - $execution = Authorization::skip(function () use ($dbForProject, &$executionId, $functionId, $deploymentId, $trigger, $userId) { + $execution = Authorization::skip(function () use ($dbForProject, &$executionId, $functionId, $deploymentId, $trigger, $user) { $execution = $dbForProject->getDocument('executions', $executionId); if ($execution->isEmpty()) { $executionId = $dbForProject->getId(); $execution = $dbForProject->createDocument('executions', new Document([ '$id' => $executionId, - '$read' => $userId ? ['user:' . $userId] : [], + '$read' => $user->getId() ? ['user:' . $user->getId()] : [], '$write' => [], 'dateCreated' => time(), 'functionId' => $functionId, @@ -268,8 +268,8 @@ class FunctionsV1 extends Worker 'APPWRITE_FUNCTION_EVENT' => $event, 'APPWRITE_FUNCTION_EVENT_DATA' => $eventData, 'APPWRITE_FUNCTION_DATA' => $data, - 'APPWRITE_FUNCTION_PROJECT_ID' => $projectId, - 'APPWRITE_FUNCTION_USER_ID' => $userId, + 'APPWRITE_FUNCTION_PROJECT_ID' => $project->getId(), + 'APPWRITE_FUNCTION_USER_ID' => $user->getId(), 'APPWRITE_FUNCTION_JWT' => $jwt, ]; $vars = \array_merge($function->getAttribute('vars', []), $vars); @@ -277,7 +277,7 @@ class FunctionsV1 extends Worker /** Execute function */ try { $executionResponse = $this->executor->createExecution( - projectId: $projectId, + projectId: $project->getId(), deploymentId: $deploymentId, path: $build->getAttribute('outputPath', ''), vars: $vars, @@ -304,30 +304,43 @@ class FunctionsV1 extends Worker } $execution = Authorization::skip(fn () => $dbForProject->updateDocument('executions', $executionId, $execution)); + /** @var Document $execution */ /** Trigger Webhook */ $executionModel = new Execution(); $executionUpdate = new Event(Event::WEBHOOK_QUEUE_NAME, Event::WEBHOOK_CLASS_NAME); $executionUpdate - ->setParam('projectId', $projectId) - ->setParam('userId', $userId) - ->setParam('event', 'functions.executions.update') - ->setParam('eventData', $execution->getArrayCopy(array_keys($executionModel->getRules()))) + ->setProject($project) + ->setUser($user) + ->setEvent('functions.[functionId].executions.[executionId].update') + ->setParam('functionId', $function->getId()) + ->setParam('executionId', $execution->getId()) + ->setPayload($execution->getArrayCopy(array_keys($executionModel->getRules()))) + ->trigger(); + + /** Trigger Functions */ + $executionUpdate + ->setClass(Event::FUNCTIONS_CLASS_NAME) + ->setQueue(Event::FUNCTIONS_QUEUE_NAME) ->trigger(); /** Trigger realtime event */ - $target = Realtime::fromPayload('functions.executions.update', $execution); + $allEvents = Event::generateEvents('functions.[functionId].executions.[executionId].update', [ + 'functionId' => $function->getId(), + 'executionId' => $execution->getId() + ]); + $target = Realtime::fromPayload($allEvents[0], $execution); Realtime::send( projectId: 'console', payload: $execution->getArrayCopy(), - event: 'functions.executions.update', + events: $allEvents, channels: $target['channels'], roles: $target['roles'] ); Realtime::send( - projectId: $projectId, + projectId: $project->getId(), payload: $execution->getArrayCopy(), - event: 'functions.executions.update', + events: $allEvents, channels: $target['channels'], roles: $target['roles'] ); @@ -338,7 +351,7 @@ class FunctionsV1 extends Worker $statsd = $register->get('statsd'); $usage = new Stats($statsd); $usage - ->setParam('projectId', $projectId) + ->setParam('projectId', $project->getId()) ->setParam('functionId', $function->getId()) ->setParam('functionExecution', 1) ->setParam('functionStatus', $execution->getAttribute('status', '')) diff --git a/app/workers/webhooks.php b/app/workers/webhooks.php index aa2e84fab7..cea58fdf68 100644 --- a/app/workers/webhooks.php +++ b/app/workers/webhooks.php @@ -32,6 +32,11 @@ class WebhooksV1 extends Worker foreach ($project->getAttribute('webhooks', []) as $webhook) { if (array_intersect($webhook->getAttribute('events', []), $events)) { + Console::log(""); + Console::log(""); + Console::log(var_export($events, true)); + Console::log(""); + Console::log(""); $this->execute($events, $payload, $webhook, $user, $project); } } @@ -68,7 +73,7 @@ class WebhooksV1 extends Worker 'X-' . APP_NAME . '-Webhook-Name: ' . $webhook->getAttribute('name', ''), 'X-' . APP_NAME . '-Webhook-User-Id: ' . $user->getId(), 'X-' . APP_NAME . '-Webhook-Project-Id: ' . $project->getId(), - 'X-' . APP_NAME . '-Webhook-Signature: ' . $webhook->getAttribute('signature') ?? 'not-yet-implemented', + 'X-' . APP_NAME . '-Webhook-Signature: ' . $webhook->getAttribute('signature', 'not-yet-implemented'), ] ); diff --git a/src/Appwrite/Event/Audit.php b/src/Appwrite/Event/Audit.php index cceaa41423..ad90e7cfa4 100644 --- a/src/Appwrite/Event/Audit.php +++ b/src/Appwrite/Event/Audit.php @@ -16,6 +16,12 @@ class Audit extends Event parent::__construct(Event::AUDITS_QUEUE_NAME, Event::AUDITS_CLASS_NAME); } + /** + * Set resource for this audit event. + * + * @param string $resource + * @return self + */ public function setResource(string $resource): self { $this->resource = $resource; @@ -23,11 +29,22 @@ class Audit extends Event return $this; } + /** + * Returns the set audit resource. + * + * @return string + */ public function getResource(): string { return $this->resource; } + /** + * Set mode for this audit event + * + * @param string $mode + * @return self + */ public function setMode(string $mode): self { $this->mode = $mode; @@ -35,11 +52,22 @@ class Audit extends Event return $this; } + /** + * Returns the set audit mode. + * + * @return string + */ public function getMode(): string { return $this->mode; } + /** + * Set user agent for this audit event. + * + * @param string $userAgent + * @return self + */ public function setUserAgent(string $userAgent): self { $this->userAgent = $userAgent; @@ -47,11 +75,22 @@ class Audit extends Event return $this; } + /** + * Returns the set audit user agent. + * + * @return string + */ public function getUserAgent(): string { return $this->userAgent; } + /** + * Set IP for this audit event. + * + * @param string $userAgent + * @return self + */ public function setIP(string $ip): self { $this->ip = $ip; @@ -59,18 +98,28 @@ class Audit extends Event return $this; } + /** + * Returns the set audit IP. + * + * @return string + */ public function getIP(): string { return $this->ip; } + /** + * Executes the event and sends it to the audit worker. + * + * @return string|bool + * @throws \InvalidArgumentException + */ public function trigger(): string|bool { return Resque::enqueue($this->queue, $this->class, [ 'project' => $this->project, 'user' => $this->user, 'payload' => $this->payload, - 'trigger' => $this->trigger, 'resource' => $this->resource, 'mode' => $this->mode, 'ip' => $this->ip, diff --git a/src/Appwrite/Event/Certificate.php b/src/Appwrite/Event/Certificate.php index 1055df697a..b01fa71984 100644 --- a/src/Appwrite/Event/Certificate.php +++ b/src/Appwrite/Event/Certificate.php @@ -7,15 +7,21 @@ use Utopia\Database\Document; class Certificate extends Event { - protected ?Document $domain = null; protected bool $validateTarget = false; protected bool $validateCNAME = false; + protected ?Document $domain = null; public function __construct() { parent::__construct(Event::CERTIFICATES_QUEUE_NAME, Event::CERTIFICATES_CLASS_NAME); } + /** + * Set domain for this certificates event. + * + * @param \Utopia\Database\Document $domain + * @return self + */ public function setDomain(Document $domain): self { $this->domain = $domain; @@ -23,11 +29,22 @@ class Certificate extends Event return $this; } + /** + * Returns the set domain for this certificate event. + * + * @return null|\Utopia\Database\Document + */ public function getDomain(): ?Document { return $this->domain; } + /** + * Set if the target needs be validated. + * + * @param bool $validateTarget + * @return self + */ public function setValidateTarget(bool $validateTarget): self { $this->validateTarget = $validateTarget; @@ -35,11 +52,22 @@ class Certificate extends Event return $this; } + /** + * Return if the domain target will be validated. + * + * @return bool + */ public function getValidateTarget(): bool { return $this->validateTarget; } + /** + * Set if the CNAME needs to be validated. + * + * @param bool $validateCNAME + * @return self + */ public function setValidateCNAME(bool $validateCNAME): self { $this->validateCNAME = $validateCNAME; @@ -47,11 +75,22 @@ class Certificate extends Event return $this; } + /** + * Return if the CNAME will be validated. + * + * @return bool + */ public function getValidateCNAME(): bool { return $this->validateCNAME; } + /** + * Executes the event and sends it to the certificates worker. + * + * @return string|bool + * @throws \InvalidArgumentException + */ public function trigger(): string|bool { return Resque::enqueue($this->queue, $this->class, [ @@ -61,4 +100,4 @@ class Certificate extends Event 'validateCNAME' => $this->validateCNAME ]); } -} \ No newline at end of file +} diff --git a/src/Appwrite/Event/Database.php b/src/Appwrite/Event/Database.php index 71700dbf4b..28123e8a03 100644 --- a/src/Appwrite/Event/Database.php +++ b/src/Appwrite/Event/Database.php @@ -16,6 +16,12 @@ class Database extends Event parent::__construct(Event::DATABASE_QUEUE_NAME, Event::DATABASE_CLASS_NAME); } + /** + * Sets the type for this database event (use the constants starting with DATABASE_TYPE_*). + * + * @param string $type + * @return self + */ public function setType(string $type): self { $this->type = $type; @@ -23,11 +29,21 @@ class Database extends Event return $this; } + /** + * Returns the set type for the database event. + * @return string + */ public function getType(): string { return $this->type; } + /** + * Set the collection for this database event. + * + * @param \Utopia\Database\Document $collection + * @return self + */ public function setCollection(Document $collection): self { $this->collection = $collection; @@ -35,11 +51,22 @@ class Database extends Event return $this; } - public function getCollection(): Document + /** + * Returns set collection for this event. + * + * @return null|\Utopia\Database\Document + */ + public function getCollection(): ?Document { return $this->collection; } + /** + * Set the document for this database event. + * + * @param \Utopia\Database\Document $document + * @return self + */ public function setDocument(Document $document): self { $this->document = $document; @@ -47,11 +74,21 @@ class Database extends Event return $this; } - public function getDocument(): Document + /** + * Returns set document for this database event. + * @return null|\Utopia\Database\Document + */ + public function getDocument(): ?Document { return $this->document; } + /** + * Executes the event and send it to the database worker. + * + * @return string|bool + * @throws \InvalidArgumentException + */ public function trigger(): string|bool { return Resque::enqueue($this->queue, $this->class, [ diff --git a/src/Appwrite/Event/Delete.php b/src/Appwrite/Event/Delete.php index 7abd7ae2bb..8b02cbd89f 100644 --- a/src/Appwrite/Event/Delete.php +++ b/src/Appwrite/Event/Delete.php @@ -15,6 +15,12 @@ class Delete extends Event parent::__construct(Event::DELETE_QUEUE_NAME, Event::DELETE_CLASS_NAME); } + /** + * Sets the type for the delete event (use the constants starting with DELETE_TYPE_*). + * + * @param string $type + * @return self + */ public function setType(string $type): self { $this->type = $type; @@ -22,11 +28,22 @@ class Delete extends Event return $this; } + /** + * Returns the set type for the delete event. + * + * @return string + */ public function getType(): string { return $this->type; } + /** + * Sets the document for the delete event. + * + * @param \Utopia\Database\Document $document + * @return self + */ public function setDocument(Document $document): self { $this->document = $document; @@ -34,11 +51,22 @@ class Delete extends Event return $this; } - public function getDocument(): Document + /** + * Returns the set document for the delete event. + * + * @return null|\Utopia\Database\Document + */ + public function getDocument(): ?Document { return $this->document; } + /** + * Executes this event and sends it to the deletes worker. + * + * @return string|bool + * @throws \InvalidArgumentException + */ public function trigger(): string|bool { return Resque::enqueue($this->queue, $this->class, [ @@ -47,4 +75,4 @@ class Delete extends Event 'document' => $this->document, ]); } -} \ No newline at end of file +} diff --git a/src/Appwrite/Event/Event.php b/src/Appwrite/Event/Event.php index c6c69b111c..7ed2136d18 100644 --- a/src/Appwrite/Event/Event.php +++ b/src/Appwrite/Event/Event.php @@ -43,7 +43,7 @@ class Event protected array $payload = []; protected ?Document $project = null; protected ?Document $user = null; - protected ?Document $trigger = null; + protected ?Document $context = null; /** * @param string $queue @@ -137,16 +137,16 @@ class Event return $this->payload; } - public function setTrigger(Document $trigger): self + public function setContext(Document $context): self { - $this->trigger = $trigger; + $this->context = $context; return $this; } - public function getTrigger(): ?Document + public function getContext(): ?Document { - return $this->trigger; + return $this->context; } /** @@ -218,7 +218,7 @@ class Event 'project' => $this->project, 'user' => $this->user, 'payload' => $this->payload, - 'trigger' => $this->trigger, + 'context' => $this->context, 'events' => Event::generateEvents($this->getEvent(), $this->getParams()) ]); } @@ -235,6 +235,12 @@ class Event return $this; } + /** + * Parses event pattern and returns the parts in their respective section. + * + * @param string $pattern + * @return array + */ public static function parseEventPattern(string $pattern): array { $parts = \explode('.', $pattern); @@ -278,6 +284,14 @@ class Event ]; } + /** + * Generates all possible events from a pattern. + * + * @param string $pattern + * @param array $params + * @return array + * @throws \InvalidArgumentException + */ static function generateEvents(string $pattern, array $params = []): array { $params = \array_filter($params, fn($param) => !\is_array($param)); @@ -313,16 +327,16 @@ class Event $patterns[] = \implode('.', [$type, $resource, $subType, $subResource, $action]); $patterns[] = \implode('.', [$type, $resource, $subType, $subResource]); } else { - if ($attribute) { - $patterns[] = \implode('.', [$type, $resource, $action, $attribute]); - } $patterns[] = \implode('.', [$type, $resource, $action]); - $patterns[] = \implode('.', [$type, $resource]); + } + if ($attribute) { + $patterns[] = \implode('.', [$type, $resource, $action, $attribute]); } } if ($subResource) { $patterns[] = \implode('.', [$type, $resource, $subType, $subResource]); } + $patterns[] = \implode('.', [$type, $resource]); /** * Removes all duplicates. diff --git a/src/Appwrite/Event/Func.php b/src/Appwrite/Event/Func.php index 3caefdd6f2..18f684c4a2 100644 --- a/src/Appwrite/Event/Func.php +++ b/src/Appwrite/Event/Func.php @@ -9,17 +9,23 @@ use Utopia\Database\Document; class Func extends Event { - protected ?Document $function = null; - protected ?Document $execution = null; protected string $jwt = ''; protected string $type = ''; protected string $data = ''; + protected ?Document $function = null; + protected ?Document $execution = null; public function __construct() { parent::__construct(Event::FUNCTIONS_QUEUE_NAME, Event::FUNCTIONS_CLASS_NAME); } + /** + * Sets function document for the function event. + * + * @param \Utopia\Database\Document $function + * @return self + */ public function setFunction(Document $function): self { $this->function = $function; @@ -27,11 +33,22 @@ class Func extends Event return $this; } + /** + * Returns set function document for the function event. + * + * @return null|\Utopia\Database\Document + */ public function getFunction(): ?Document { return $this->function; } + /** + * Sets execution for the function event. + * + * @param \Utopia\Database\Document $execution + * @return self + */ public function setExecution(Document $execution): self { $this->execution = $execution; @@ -39,11 +56,22 @@ class Func extends Event return $this; } + /** + * Returns set execution for the function event. + * + * @return null|\Utopia\Database\Document + */ public function getExecution(): ?Document { return $this->execution; } + /** + * Sets type for the function event. + * + * @param string $type Can be `schedule`, `event` or `http`. + * @return self + */ public function setType(string $type): self { $this->type = $type; @@ -51,11 +79,22 @@ class Func extends Event return $this; } + /** + * Returns set type for the function event. + * + * @return string + */ public function getType(): string { return $this->type; } + /** + * Sets custom data for the function event. + * + * @param string $data + * @return self + */ public function setData(string $data): self { $this->data = $data; @@ -63,11 +102,22 @@ class Func extends Event return $this; } + /** + * Returns set custom data for the function event. + * + * @return string + */ public function getData(): string { return $this->data; } + /** + * Sets JWT for the function event. + * + * @param string $jwt + * @return self + */ public function setJWT(string $jwt): self { $this->jwt = $jwt; @@ -75,11 +125,22 @@ class Func extends Event return $this; } + /** + * Returns set JWT for the function event. + * + * @return string + */ public function getJWT(): string { return $this->jwt; } + /** + * Executes the function event and sends it to the functions worker. + * + * @return string|bool + * @throws \InvalidArgumentException + */ public function trigger(): string|bool { return Resque::enqueue($this->queue, $this->class, [ @@ -94,6 +155,14 @@ class Func extends Event ]); } + /** + * Schedules the function event and schedules it in the functions worker queue. + * + * @param \DateTime|int $at + * @return void + * @throws \Resque_Exception + * @throws \ResqueScheduler_InvalidTimestampException + */ public function schedule(DateTime|int $at): void { ResqueScheduler::enqueueAt($at, $this->queue, $this->class, [ @@ -106,4 +175,4 @@ class Func extends Event 'data' => $this->data ]); } -} \ No newline at end of file +} diff --git a/src/Appwrite/Event/Mail.php b/src/Appwrite/Event/Mail.php index 155e853cc5..28df2c6b89 100644 --- a/src/Appwrite/Event/Mail.php +++ b/src/Appwrite/Event/Mail.php @@ -19,6 +19,12 @@ class Mail extends Event parent::__construct(Event::MAILS_QUEUE_NAME, Event::MAILS_CLASS_NAME); } + /** + * Sets team for the mail event. + * + * @param \Utopia\Database\Document $team + * @return self + */ public function setTeam(Document $team): self { $this->team = $team; @@ -26,11 +32,22 @@ class Mail extends Event return $this; } - public function getTeam(): Document + /** + * Returns set team for the mail event. + * + * @return null|\Utopia\Database\Document + */ + public function getTeam(): ?Document { return $this->team; } + /** + * Sets recipient for the mail event. + * + * @param string $recipient + * @return self + */ public function setRecipient(string $recipient): self { $this->recipient = $recipient; @@ -38,11 +55,22 @@ class Mail extends Event return $this; } + /** + * Returns set recipient for mail event. + * + * @return string + */ public function getRecipient(): string { return $this->recipient; } + /** + * Sets url for the mail event. + * + * @param string $url + * @return self + */ public function setUrl(string $url): self { $this->url = $url; @@ -50,11 +78,22 @@ class Mail extends Event return $this; } + /** + * Returns set url for the mail event. + * + * @return string + */ public function getURL(): string { return $this->url; } + /** + * Sets type for the mail event (use the constants starting with MAIL_TYPE_*). + * + * @param string $type + * @return self + */ public function setType(string $type): self { $this->type = $type; @@ -62,11 +101,22 @@ class Mail extends Event return $this; } + /** + * Returns set type for the mail event. + * + * @return string + */ public function getType(): string { return $this->type; } + /** + * Sets name for the mail event. + * + * @param string $name + * @return self + */ public function setName(string $name): self { $this->name = $name; @@ -74,11 +124,22 @@ class Mail extends Event return $this; } + /** + * Returns set name for the mail event. + * + * @return string + */ public function getName(): string { return $this->name; } + /** + * Sets locale for the mail event. + * + * @param string $locale + * @return self + */ public function setLocale(string $locale): self { $this->locale = $locale; @@ -86,18 +147,28 @@ class Mail extends Event return $this; } + /** + * Returns set locale for the mail event. + * + * @return string + */ public function getLocale(): string { return $this->locale; } + /** + * Executes the event and sends it to the mails worker. + * + * @return string|bool + * @throws \InvalidArgumentException + */ public function trigger(): string|bool { return Resque::enqueue($this->queue, $this->class, [ 'project' => $this->project, 'user' => $this->user, 'payload' => $this->payload, - 'trigger' => $this->trigger, 'recipient' => $this->recipient, 'url' => $this->url, 'locale' => $this->locale, @@ -107,4 +178,4 @@ class Mail extends Event 'events' => Event::generateEvents($this->getEvent(), $this->getParams()) ]); } -} \ No newline at end of file +} diff --git a/src/Appwrite/Event/Validator/Event.php b/src/Appwrite/Event/Validator/Event.php index 66086bc946..29dec12f39 100644 --- a/src/Appwrite/Event/Validator/Event.php +++ b/src/Appwrite/Event/Validator/Event.php @@ -4,11 +4,6 @@ namespace Appwrite\Event\Validator; use Utopia\Validator; -/** - * Password. - * - * Validates user password string - */ class Event extends Validator { protected array $types = [ @@ -60,6 +55,7 @@ class Event extends Validator 'update', 'delete' ]; + /** * Get Description. * @@ -69,7 +65,7 @@ class Event extends Validator */ public function getDescription(): string { - return 'Password must be at least 8 characters'; + return 'Event is not valid.'; } /** @@ -89,7 +85,7 @@ class Event extends Validator } /** - * Identify all sestions of the pattern. + * Identify all sections of the pattern. */ $type = $parts[0] ?? false; $resource = $parts[1] ?? false; @@ -114,6 +110,7 @@ class Event extends Validator $subType ??= false; $subResource ??= false; $attribute ??= false; + $action = match (true) { !$hasSubResource && $count > 2 => $parts[2], $hasSubResource && $count > 4 => $parts[4], diff --git a/src/Appwrite/Messaging/Adapter.php b/src/Appwrite/Messaging/Adapter.php index 6ef2d5cfde..1910847245 100644 --- a/src/Appwrite/Messaging/Adapter.php +++ b/src/Appwrite/Messaging/Adapter.php @@ -6,5 +6,5 @@ abstract class Adapter { public abstract function subscribe(string $projectId, mixed $identifier, array $roles, array $channels): void; public abstract function unsubscribe(mixed $identifier): void; - public static abstract function send(string $projectId, array $payload, string $event, array $channels, array $roles, array $options): void; + public static abstract function send(string $projectId, array $payload, array $events, array $channels, array $roles, array $options): void; } diff --git a/src/Appwrite/Messaging/Adapter/Realtime.php b/src/Appwrite/Messaging/Adapter/Realtime.php index e2314c4fdd..10667f4765 100644 --- a/src/Appwrite/Messaging/Adapter/Realtime.php +++ b/src/Appwrite/Messaging/Adapter/Realtime.php @@ -127,7 +127,7 @@ class Realtime extends Adapter * @param array $options * @return void */ - public static function send(string $projectId, array $payload, string $event, array $channels, array $roles, array $options = []): void + public static function send(string $projectId, array $payload, array $events, array $channels, array $roles, array $options = []): void { if (empty($channels) || empty($roles) || empty($projectId)) return; @@ -142,7 +142,7 @@ class Realtime extends Adapter 'permissionsChanged' => $permissionsChanged, 'userId' => $userId, 'data' => [ - 'event' => $event, + 'events' => $events, 'channels' => $channels, 'timestamp' => time(), 'payload' => $payload @@ -287,13 +287,13 @@ class Realtime extends Adapter break; case 'buckets': if ($parts[2] === 'files') { - if($bucket->isEmpty()) { + if ($bucket->isEmpty()) { throw new \Exception('Bucket needs to be pased to Realtime for File events in the Storage.'); } $channels[] = 'files'; $channels[] = 'buckets.' . $payload->getAttribute('bucketId') . '.files'; $channels[] = 'buckets.' . $payload->getAttribute('bucketId') . '.files.' . $payload->getId(); - $roles = ($bucket->getAttribute('permission') === 'collection') ? $bucket->getRead() : $payload->getRead(); + $roles = ($bucket->getAttribute('permission') === 'bucket') ? $bucket->getRead() : $payload->getRead(); } break; diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index 5d1b0937fa..e0a7cb158a 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -81,7 +81,7 @@ class ProjectsConsoleClientTest extends Scope /** * @depends testCreateProject */ - public function testListProject($data):array + public function testListProject($data): array { $id = $data['projectId'] ?? ''; @@ -174,7 +174,7 @@ class ProjectsConsoleClientTest extends Scope $response = $this->client->call(Client::METHOD_GET, '/projects', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()),[ + ], $this->getHeaders()), [ 'cursor' => $response['body']['projects'][0]['$id'] ]); @@ -186,7 +186,7 @@ class ProjectsConsoleClientTest extends Scope $response = $this->client->call(Client::METHOD_GET, '/projects', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()),[ + ], $this->getHeaders()), [ 'cursor' => $response['body']['projects'][0]['$id'], 'cursorDirection' => Database::CURSOR_BEFORE ]); @@ -202,7 +202,7 @@ class ProjectsConsoleClientTest extends Scope $response = $this->client->call(Client::METHOD_GET, '/projects', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()),[ + ], $this->getHeaders()), [ 'cursor' => 'unknown' ]); @@ -214,14 +214,14 @@ class ProjectsConsoleClientTest extends Scope /** * @depends testCreateProject */ - public function testGetProject($data):array + public function testGetProject($data): array { $id = $data['projectId'] ?? ''; /** * Test for SUCCESS */ - $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id, array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -255,14 +255,14 @@ class ProjectsConsoleClientTest extends Scope /** * @depends testCreateProject */ - public function testGetProjectUsage($data):array + public function testGetProjectUsage($data): array { $id = $data['projectId'] ?? ''; /** * Test for SUCCESS */ - $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/usage', array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/usage', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -303,14 +303,14 @@ class ProjectsConsoleClientTest extends Scope /** * @depends testGetProjectUsage */ - public function testUpdateProject($data):array + public function testUpdateProject($data): array { $id = $data['projectId'] ?? ''; /** * Test for SUCCESS */ - $response = $this->client->call(Client::METHOD_PATCH, '/projects/'.$id, array_merge([ + $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -330,7 +330,7 @@ class ProjectsConsoleClientTest extends Scope /** * Test for FAILURE */ - + $response = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -347,7 +347,7 @@ class ProjectsConsoleClientTest extends Scope /** * @depends testGetProjectUsage */ - public function testUpdateProjectOAuth($data):array + public function testUpdateProjectOAuth($data): array { $id = $data['projectId'] ?? ''; $providers = require('app/config/providers.php'); @@ -357,20 +357,20 @@ class ProjectsConsoleClientTest extends Scope */ foreach ($providers as $key => $provider) { - $response = $this->client->call(Client::METHOD_PATCH, '/projects/'.$id.'/oauth2', array_merge([ + $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/oauth2', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'provider' => $key, - 'appId' => 'AppId-'.ucfirst($key), - 'secret' => 'Secret-'.ucfirst($key), + 'appId' => 'AppId-' . ucfirst($key), + 'secret' => 'Secret-' . ucfirst($key), ]); - + $this->assertEquals(200, $response['headers']['status-code']); $this->assertNotEmpty($response['body']['$id']); } - $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id, array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -380,15 +380,15 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals($id, $response['body']['$id']); foreach ($providers as $key => $provider) { - $this->assertEquals('AppId-'.ucfirst($key), $response['body']['provider'.ucfirst($key).'Appid']); - $this->assertEquals('Secret-'.ucfirst($key), $response['body']['provider'.ucfirst($key).'Secret']); + $this->assertEquals('AppId-' . ucfirst($key), $response['body']['provider' . ucfirst($key) . 'Appid']); + $this->assertEquals('Secret-' . ucfirst($key), $response['body']['provider' . ucfirst($key) . 'Secret']); } /** * Test for FAILURE */ - - $response = $this->client->call(Client::METHOD_PATCH, '/projects/'.$id.'/oauth2', array_merge([ + + $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/oauth2', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -405,15 +405,15 @@ class ProjectsConsoleClientTest extends Scope /** * @depends testGetProjectUsage */ - public function testUpdateProjectAuthStatus($data):array + public function testUpdateProjectAuthStatus($data): array { $id = $data['projectId'] ?? ''; $auth = require('app/config/auth.php'); - - $originalEmail = uniqid().'user@localhost.test'; + + $originalEmail = uniqid() . 'user@localhost.test'; $originalPassword = 'password'; $originalName = 'User Name'; - + $response = $this->client->call(Client::METHOD_POST, '/account', array_merge([ 'origin' => 'http://localhost', 'content-type' => 'application/json', @@ -434,33 +434,33 @@ class ProjectsConsoleClientTest extends Scope 'password' => $originalPassword, ]); - $session = $this->client->parseCookie((string)$response['headers']['set-cookie'])['a_session_'.$id]; + $session = $this->client->parseCookie((string)$response['headers']['set-cookie'])['a_session_' . $id]; /** * Test for SUCCESS */ foreach ($auth as $index => $method) { - $response = $this->client->call(Client::METHOD_PATCH, '/projects/'.$id.'/auth/'.$index, array_merge([ + $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/auth/' . $index, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'status' => false, ]); - + $this->assertEquals(200, $response['headers']['status-code']); $this->assertNotEmpty($response['body']['$id']); - $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id, array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); $this->assertEquals(200, $response['headers']['status-code']); $this->assertNotEmpty($response['body']['$id']); - $this->assertEquals(false, $response['body']['auth'. ucfirst($method['key'])]); + $this->assertEquals(false, $response['body']['auth' . ucfirst($method['key'])]); } - - $email = uniqid().'user@localhost.test'; + + $email = uniqid() . 'user@localhost.test'; $password = 'password'; $name = 'User Name'; @@ -483,7 +483,7 @@ class ProjectsConsoleClientTest extends Scope $response = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $id, - 'cookie' => 'a_session_'.$id.'='.$session, + 'cookie' => 'a_session_' . $id . '=' . $session, ]), [ 'teamId' => 'unique()', 'name' => 'Arsenal' @@ -493,10 +493,10 @@ class ProjectsConsoleClientTest extends Scope $teamUid = $response['body']['$id']; - $response = $this->client->call(Client::METHOD_POST, '/teams/'.$teamUid.'/memberships', array_merge([ + $response = $this->client->call(Client::METHOD_POST, '/teams/' . $teamUid . '/memberships', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $id, - 'cookie' => 'a_session_'.$id.'=' . $session, + 'cookie' => 'a_session_' . $id . '=' . $session, ]), [ 'email' => $email, 'name' => 'Friend User', @@ -509,7 +509,7 @@ class ProjectsConsoleClientTest extends Scope $response = $this->client->call(Client::METHOD_POST, '/account/jwt', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $id, - 'cookie' => 'a_session_'.$id.'=' . $session, + 'cookie' => 'a_session_' . $id . '=' . $session, ])); $this->assertEquals($response['headers']['status-code'], 501); @@ -522,7 +522,7 @@ class ProjectsConsoleClientTest extends Scope 'email' => $originalEmail, 'password' => $originalPassword, ]); - + $this->assertEquals($response['headers']['status-code'], 501); $response = $this->client->call(Client::METHOD_POST, '/account/anonymous', array_merge([ @@ -536,7 +536,7 @@ class ProjectsConsoleClientTest extends Scope // Cleanup foreach ($auth as $index => $method) { - $response = $this->client->call(Client::METHOD_PATCH, '/projects/'.$id.'/auth/'.$index, array_merge([ + $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/auth/' . $index, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -550,14 +550,14 @@ class ProjectsConsoleClientTest extends Scope /** * @depends testGetProjectUsage */ - public function testUpdateProjectAuthLimit($data):array + public function testUpdateProjectAuthLimit($data): array { $id = $data['projectId'] ?? ''; - + /** * Test for SUCCESS */ - $response = $this->client->call(Client::METHOD_PATCH, '/projects/'.$id.'/auth/limit', array_merge([ + $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/auth/limit', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -566,8 +566,8 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); $this->assertNotEmpty($response['body']['$id']); - - $email = uniqid().'user@localhost.test'; + + $email = uniqid() . 'user@localhost.test'; $password = 'password'; $name = 'User Name'; @@ -590,7 +590,7 @@ class ProjectsConsoleClientTest extends Scope /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_PATCH, '/projects/'.$id.'/auth/limit', array_merge([ + $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/auth/limit', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -599,7 +599,7 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); $this->assertNotEmpty($response['body']['$id']); - + $response = $this->client->call(Client::METHOD_POST, '/account', array_merge([ 'origin' => 'http://localhost', 'content-type' => 'application/json', @@ -649,13 +649,13 @@ class ProjectsConsoleClientTest extends Scope * Test for Disabled */ foreach ($services as $service) { - if(!$service['optional']) { + if (!$service['optional']) { continue; } $key = $service['key'] ?? ''; - - $response = $this->client->call(Client::METHOD_PATCH, '/projects/'.$id.'/service', array_merge([ + + $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/service', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'cookie' => 'a_session_console=' . $this->getRoot()['session'], @@ -667,7 +667,7 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); $this->assertNotEmpty($response['body']['$id']); - $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id, array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'cookie' => 'a_session_console=' . $this->getRoot()['session'], @@ -675,13 +675,13 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); $this->assertNotEmpty($response['body']['$id']); - $this->assertEquals(false, $response['body']['serviceStatusFor'.ucfirst($key)]); + $this->assertEquals(false, $response['body']['serviceStatusFor' . ucfirst($key)]); } - + /** * Admin request must succeed */ - + $response = $this->client->call(Client::METHOD_GET, '/functions', array_merge([ 'origin' => 'http://localhost', 'content-type' => 'application/json', @@ -690,17 +690,17 @@ class ProjectsConsoleClientTest extends Scope 'cookie' => 'a_session_console=' . $this->getRoot()['session'], 'x-appwrite-mode' => 'admin' ])); - + $this->assertEquals(200, $response['headers']['status-code']); foreach ($services as $service) { - if(!$service['optional']) { + if (!$service['optional']) { continue; } $key = $service['key'] ?? ''; - $response = $this->client->call(Client::METHOD_PATCH, '/projects/'.$id.'/service/', array_merge([ + $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/service/', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -713,7 +713,7 @@ class ProjectsConsoleClientTest extends Scope } /** @depends testUpdateProjectServiceStatusAdmin */ - public function testUpdateProjectServiceStatus($data):void + public function testUpdateProjectServiceStatus($data): void { $id = $data['projectId']; @@ -723,13 +723,13 @@ class ProjectsConsoleClientTest extends Scope * Test for Disabled */ foreach ($services as $service) { - if(!$service['optional']) { + if (!$service['optional']) { continue; } $key = $service['key'] ?? ''; - $response = $this->client->call(Client::METHOD_PATCH, '/projects/'.$id.'/service', array_merge([ + $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/service', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'cookie' => 'a_session_console=' . $this->getRoot()['session'], @@ -741,7 +741,7 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); $this->assertNotEmpty($response['body']['$id']); - $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id, array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'cookie' => 'a_session_console=' . $this->getRoot()['session'], @@ -749,7 +749,7 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); $this->assertNotEmpty($response['body']['$id']); - $this->assertEquals(false, $response['body']['serviceStatusFor'.ucfirst($key)]); + $this->assertEquals(false, $response['body']['serviceStatusFor' . ucfirst($key)]); } /** @@ -776,7 +776,7 @@ class ProjectsConsoleClientTest extends Scope // Cleanup foreach ($services as $service) { - $response = $this->client->call(Client::METHOD_PATCH, '/projects/'.$id.'/service/', array_merge([ + $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/service/', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -793,12 +793,12 @@ class ProjectsConsoleClientTest extends Scope { $id = $data['projectId'] ?? ''; - $response = $this->client->call(Client::METHOD_POST, '/projects/'.$id.'/webhooks', array_merge([ + $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/webhooks', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'name' => 'Webhook Test', - 'events' => ['account.create', 'account.update.email'], + 'events' => ['users.*.create', 'users.*.update.email'], 'url' => 'https://appwrite.io', 'security' => true, 'httpUser' => 'username', @@ -807,25 +807,25 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(201, $response['headers']['status-code']); $this->assertNotEmpty($response['body']['$id']); - $this->assertContains('account.create', $response['body']['events']); - $this->assertContains('account.update.email', $response['body']['events']); + $this->assertContains('users.*.create', $response['body']['events']); + $this->assertContains('users.*.update.email', $response['body']['events']); $this->assertCount(2, $response['body']['events']); $this->assertEquals('https://appwrite.io', $response['body']['url']); $this->assertIsBool($response['body']['security']); $this->assertEquals(true, $response['body']['security']); $this->assertEquals('username', $response['body']['httpUser']); - + $data = array_merge($data, ['webhookId' => $response['body']['$id']]); /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_POST, '/projects/'.$id.'/webhooks', array_merge([ + $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/webhooks', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'name' => 'Webhook Test', - 'events' => ['account.unknown', 'account.update.email'], + 'events' => ['account.unknown', 'users.*.update.email'], 'url' => 'https://appwrite.io', 'security' => true, 'httpUser' => 'username', @@ -834,12 +834,12 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(400, $response['headers']['status-code']); - $response = $this->client->call(Client::METHOD_POST, '/projects/'.$id.'/webhooks', array_merge([ + $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/webhooks', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'name' => 'Webhook Test', - 'events' => ['account.create', 'account.update.email'], + 'events' => ['users.*.create', 'users.*.update.email'], 'url' => 'invalid://appwrite.io', ]); @@ -855,14 +855,14 @@ class ProjectsConsoleClientTest extends Scope { $id = $data['projectId'] ?? ''; - $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/webhooks', array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/webhooks', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals(1, $response['body']['total']); - + /** * Test for FAILURE */ @@ -878,7 +878,7 @@ class ProjectsConsoleClientTest extends Scope $id = $data['projectId'] ?? ''; $webhookId = $data['webhookId'] ?? ''; - $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/webhooks/'.$webhookId, array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/webhooks/' . $webhookId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -886,17 +886,17 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); $this->assertNotEmpty($response['body']['$id']); $this->assertEquals($webhookId, $response['body']['$id']); - $this->assertContains('account.create', $response['body']['events']); - $this->assertContains('account.update.email', $response['body']['events']); + $this->assertContains('users.*.create', $response['body']['events']); + $this->assertContains('users.*.update.email', $response['body']['events']); $this->assertCount(2, $response['body']['events']); $this->assertEquals('https://appwrite.io', $response['body']['url']); $this->assertEquals('username', $response['body']['httpUser']); $this->assertEquals('password', $response['body']['httpPass']); - + /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/webhooks/error', array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/webhooks/error', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -914,12 +914,12 @@ class ProjectsConsoleClientTest extends Scope $id = $data['projectId'] ?? ''; $webhookId = $data['webhookId'] ?? ''; - $response = $this->client->call(Client::METHOD_PUT, '/projects/'.$id.'/webhooks/'.$webhookId, array_merge([ + $response = $this->client->call(Client::METHOD_PUT, '/projects/' . $id . '/webhooks/' . $webhookId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'name' => 'Webhook Test Update', - 'events' => ['account.delete', 'account.sessions.delete', 'storage.files.create'], + 'events' => ['users.*.delete', 'users.*.sessions.*.delete', 'buckets.*.files.*.create'], 'url' => 'https://appwrite.io/new', 'security' => false, 'httpUser' => '', @@ -930,9 +930,9 @@ class ProjectsConsoleClientTest extends Scope $this->assertNotEmpty($response['body']['$id']); $this->assertEquals($webhookId, $response['body']['$id']); $this->assertEquals('Webhook Test Update', $response['body']['name']); - $this->assertContains('account.delete', $response['body']['events']); - $this->assertContains('account.sessions.delete', $response['body']['events']); - $this->assertContains('storage.files.create', $response['body']['events']); + $this->assertContains('users.*.delete', $response['body']['events']); + $this->assertContains('users.*.sessions.*.delete', $response['body']['events']); + $this->assertContains('buckets.*.files.*.create', $response['body']['events']); $this->assertCount(3, $response['body']['events']); $this->assertEquals('https://appwrite.io/new', $response['body']['url']); $this->assertIsBool($response['body']['security']); @@ -940,7 +940,7 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals('', $response['body']['httpUser']); $this->assertEquals('', $response['body']['httpPass']); - $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/webhooks/'.$webhookId, array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/webhooks/' . $webhookId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -949,25 +949,25 @@ class ProjectsConsoleClientTest extends Scope $this->assertNotEmpty($response['body']['$id']); $this->assertEquals($webhookId, $response['body']['$id']); $this->assertEquals('Webhook Test Update', $response['body']['name']); - $this->assertContains('account.delete', $response['body']['events']); - $this->assertContains('account.sessions.delete', $response['body']['events']); - $this->assertContains('storage.files.create', $response['body']['events']); + $this->assertContains('users.*.delete', $response['body']['events']); + $this->assertContains('users.*.sessions.*.delete', $response['body']['events']); + $this->assertContains('buckets.*.files.*.create', $response['body']['events']); $this->assertCount(3, $response['body']['events']); $this->assertEquals('https://appwrite.io/new', $response['body']['url']); $this->assertIsBool($response['body']['security']); $this->assertEquals(false, $response['body']['security']); $this->assertEquals('', $response['body']['httpUser']); $this->assertEquals('', $response['body']['httpPass']); - + /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_PUT, '/projects/'.$id.'/webhooks/'.$webhookId, array_merge([ + $response = $this->client->call(Client::METHOD_PUT, '/projects/' . $id . '/webhooks/' . $webhookId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'name' => 'Webhook Test Update', - 'events' => ['account.delete', 'account.sessions.delete', 'storage.files.create', 'unknown'], + 'events' => ['users.*.delete', 'users.*.sessions.*.delete', 'buckets.*.files.*.unknown'], 'url' => 'https://appwrite.io/new', 'security' => false, 'httpUser' => '', @@ -976,12 +976,12 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(400, $response['headers']['status-code']); - $response = $this->client->call(Client::METHOD_PUT, '/projects/'.$id.'/webhooks/'.$webhookId, array_merge([ + $response = $this->client->call(Client::METHOD_PUT, '/projects/' . $id . '/webhooks/' . $webhookId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'name' => 'Webhook Test Update', - 'events' => ['account.delete', 'account.sessions.delete', 'storage.files.create'], + 'events' => ['users.*.delete', 'users.*.sessions.*.delete', 'buckets.*.files.*.create'], 'url' => 'appwrite.io/new', 'security' => false, 'httpUser' => '', @@ -990,12 +990,12 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(400, $response['headers']['status-code']); - $response = $this->client->call(Client::METHOD_PUT, '/projects/'.$id.'/webhooks/'.$webhookId, array_merge([ + $response = $this->client->call(Client::METHOD_PUT, '/projects/' . $id . '/webhooks/' . $webhookId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'name' => 'Webhook Test Update', - 'events' => ['account.delete', 'account.sessions.delete', 'storage.files.create'], + 'events' => ['users.*.delete', 'users.*.sessions.*.delete', 'buckets.*.files.*.create'], 'url' => 'invalid://appwrite.io/new', ]); @@ -1012,7 +1012,7 @@ class ProjectsConsoleClientTest extends Scope $id = $data['projectId'] ?? ''; $webhookId = $data['webhookId'] ?? ''; - $response = $this->client->call(Client::METHOD_DELETE, '/projects/'.$id.'/webhooks/'.$webhookId, array_merge([ + $response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $id . '/webhooks/' . $webhookId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -1020,17 +1020,17 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(204, $response['headers']['status-code']); $this->assertEmpty($response['body']); - $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/webhooks/'.$webhookId, array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/webhooks/' . $webhookId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); $this->assertEquals(404, $response['headers']['status-code']); - + /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_DELETE, '/projects/'.$id.'/webhooks/error', array_merge([ + $response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $id . '/webhooks/error', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -1049,7 +1049,7 @@ class ProjectsConsoleClientTest extends Scope { $id = $data['projectId'] ?? ''; - $response = $this->client->call(Client::METHOD_POST, '/projects/'.$id.'/keys', array_merge([ + $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/keys', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -1063,13 +1063,13 @@ class ProjectsConsoleClientTest extends Scope $this->assertContains('teams.read', $response['body']['scopes']); $this->assertContains('teams.write', $response['body']['scopes']); $this->assertNotEmpty($response['body']['secret']); - + $data = array_merge($data, ['keyId' => $response['body']['$id']]); /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_POST, '/projects/'.$id.'/keys', array_merge([ + $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/keys', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -1089,14 +1089,14 @@ class ProjectsConsoleClientTest extends Scope { $id = $data['projectId'] ?? ''; - $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/keys', array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/keys', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals(1, $response['body']['total']); - + /** * Test for FAILURE */ @@ -1112,7 +1112,7 @@ class ProjectsConsoleClientTest extends Scope $id = $data['projectId'] ?? ''; $keyId = $data['keyId'] ?? ''; - $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/keys/'.$keyId, array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/keys/' . $keyId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -1125,11 +1125,11 @@ class ProjectsConsoleClientTest extends Scope $this->assertContains('teams.write', $response['body']['scopes']); $this->assertCount(2, $response['body']['scopes']); $this->assertNotEmpty($response['body']['secret']); - + /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/keys/error', array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/keys/error', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -1147,7 +1147,7 @@ class ProjectsConsoleClientTest extends Scope $id = $data['projectId'] ?? ''; $keyId = $data['keyId'] ?? ''; - $response = $this->client->call(Client::METHOD_PUT, '/projects/'.$id.'/keys/'.$keyId, array_merge([ + $response = $this->client->call(Client::METHOD_PUT, '/projects/' . $id . '/keys/' . $keyId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -1164,7 +1164,7 @@ class ProjectsConsoleClientTest extends Scope $this->assertContains('collections.read', $response['body']['scopes']); $this->assertCount(3, $response['body']['scopes']); - $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/keys/'.$keyId, array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/keys/' . $keyId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -1177,11 +1177,11 @@ class ProjectsConsoleClientTest extends Scope $this->assertContains('users.write', $response['body']['scopes']); $this->assertContains('collections.read', $response['body']['scopes']); $this->assertCount(3, $response['body']['scopes']); - + /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_PUT, '/projects/'.$id.'/keys/'.$keyId, array_merge([ + $response = $this->client->call(Client::METHOD_PUT, '/projects/' . $id . '/keys/' . $keyId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -1202,7 +1202,7 @@ class ProjectsConsoleClientTest extends Scope $id = $data['projectId'] ?? ''; $keyId = $data['keyId'] ?? ''; - $response = $this->client->call(Client::METHOD_DELETE, '/projects/'.$id.'/keys/'.$keyId, array_merge([ + $response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $id . '/keys/' . $keyId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -1210,17 +1210,17 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(204, $response['headers']['status-code']); $this->assertEmpty($response['body']); - $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/keys/'.$keyId, array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/keys/' . $keyId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); $this->assertEquals(404, $response['headers']['status-code']); - + /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_DELETE, '/projects/'.$id.'/keys/error', array_merge([ + $response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $id . '/keys/error', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -1239,7 +1239,7 @@ class ProjectsConsoleClientTest extends Scope { $id = $data['projectId'] ?? ''; - $response = $this->client->call(Client::METHOD_POST, '/projects/'.$id.'/platforms', array_merge([ + $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/platforms', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -1257,10 +1257,10 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals('', $response['body']['key']); $this->assertEquals('', $response['body']['store']); $this->assertEquals('localhost', $response['body']['hostname']); - + $data = array_merge($data, ['platformWebId' => $response['body']['$id']]); - $response = $this->client->call(Client::METHOD_POST, '/projects/'.$id.'/platforms', array_merge([ + $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/platforms', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -1278,10 +1278,10 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals('com.example.ios', $response['body']['key']); $this->assertEquals('', $response['body']['store']); $this->assertEquals('', $response['body']['hostname']); - + $data = array_merge($data, ['platformFultteriOSId' => $response['body']['$id']]); - $response = $this->client->call(Client::METHOD_POST, '/projects/'.$id.'/platforms', array_merge([ + $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/platforms', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -1299,10 +1299,10 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals('com.example.android', $response['body']['key']); $this->assertEquals('', $response['body']['store']); $this->assertEquals('', $response['body']['hostname']); - + $data = array_merge($data, ['platformFultterAndroidId' => $response['body']['$id']]); - $response = $this->client->call(Client::METHOD_POST, '/projects/'.$id.'/platforms', array_merge([ + $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/platforms', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -1323,7 +1323,7 @@ class ProjectsConsoleClientTest extends Scope $data = array_merge($data, ['platformAppleIosId' => $response['body']['$id']]); - $response = $this->client->call(Client::METHOD_POST, '/projects/'.$id.'/platforms', array_merge([ + $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/platforms', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -1344,7 +1344,7 @@ class ProjectsConsoleClientTest extends Scope $data = array_merge($data, ['platformAppleMacOsId' => $response['body']['$id']]); - $response = $this->client->call(Client::METHOD_POST, '/projects/'.$id.'/platforms', array_merge([ + $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/platforms', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -1365,7 +1365,7 @@ class ProjectsConsoleClientTest extends Scope $data = array_merge($data, ['platformAppleWatchOsId' => $response['body']['$id']]); - $response = $this->client->call(Client::METHOD_POST, '/projects/'.$id.'/platforms', array_merge([ + $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/platforms', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -1389,7 +1389,7 @@ class ProjectsConsoleClientTest extends Scope /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_POST, '/projects/'.$id.'/platforms', array_merge([ + $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/platforms', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -1414,7 +1414,7 @@ class ProjectsConsoleClientTest extends Scope sleep(1); - $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/platforms', array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/platforms', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -1425,7 +1425,7 @@ class ProjectsConsoleClientTest extends Scope /** * Test for FAILURE */ - + return $data; } @@ -1438,7 +1438,7 @@ class ProjectsConsoleClientTest extends Scope $platformWebId = $data['platformWebId'] ?? ''; - $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/platforms/'.$platformWebId, array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/platforms/' . $platformWebId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -1451,10 +1451,10 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals('', $response['body']['key']); $this->assertEquals('', $response['body']['store']); $this->assertEquals('localhost', $response['body']['hostname']); - + $platformFultteriOSId = $data['platformFultteriOSId'] ?? ''; - $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/platforms/'.$platformFultteriOSId, array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/platforms/' . $platformFultteriOSId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -1467,10 +1467,10 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals('com.example.ios', $response['body']['key']); $this->assertEquals('', $response['body']['store']); $this->assertEquals('', $response['body']['hostname']); - + $platformFultterAndroidId = $data['platformFultterAndroidId'] ?? ''; - $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/platforms/'.$platformFultterAndroidId, array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/platforms/' . $platformFultterAndroidId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -1486,7 +1486,7 @@ class ProjectsConsoleClientTest extends Scope $platformAppleIosId = $data['platformAppleIosId'] ?? ''; - $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/platforms/'.$platformAppleIosId, array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/platforms/' . $platformAppleIosId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -1502,7 +1502,7 @@ class ProjectsConsoleClientTest extends Scope $platformAppleMacOsId = $data['platformAppleMacOsId'] ?? ''; - $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/platforms/'.$platformAppleMacOsId, array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/platforms/' . $platformAppleMacOsId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -1518,7 +1518,7 @@ class ProjectsConsoleClientTest extends Scope $platformAppleWatchOsId = $data['platformAppleWatchOsId'] ?? ''; - $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/platforms/'.$platformAppleWatchOsId, array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/platforms/' . $platformAppleWatchOsId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -1534,7 +1534,7 @@ class ProjectsConsoleClientTest extends Scope $platformAppleTvOsId = $data['platformAppleTvOsId'] ?? ''; - $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/platforms/'.$platformAppleTvOsId, array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/platforms/' . $platformAppleTvOsId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -1551,7 +1551,7 @@ class ProjectsConsoleClientTest extends Scope /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/platforms/error', array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/platforms/error', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -1570,7 +1570,7 @@ class ProjectsConsoleClientTest extends Scope $platformWebId = $data['platformWebId'] ?? ''; - $response = $this->client->call(Client::METHOD_PUT, '/projects/'.$id.'/platforms/'.$platformWebId, array_merge([ + $response = $this->client->call(Client::METHOD_PUT, '/projects/' . $id . '/platforms/' . $platformWebId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -1591,7 +1591,7 @@ class ProjectsConsoleClientTest extends Scope $platformFultteriOSId = $data['platformFultteriOSId'] ?? ''; - $response = $this->client->call(Client::METHOD_PUT, '/projects/'.$id.'/platforms/'.$platformFultteriOSId, array_merge([ + $response = $this->client->call(Client::METHOD_PUT, '/projects/' . $id . '/platforms/' . $platformFultteriOSId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -1609,10 +1609,10 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals('com.example.ios2', $response['body']['key']); $this->assertEquals('', $response['body']['store']); $this->assertEquals('', $response['body']['hostname']); - + $platformFultterAndroidId = $data['platformFultterAndroidId'] ?? ''; - $response = $this->client->call(Client::METHOD_PUT, '/projects/'.$id.'/platforms/'.$platformFultterAndroidId, array_merge([ + $response = $this->client->call(Client::METHOD_PUT, '/projects/' . $id . '/platforms/' . $platformFultterAndroidId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -1633,7 +1633,7 @@ class ProjectsConsoleClientTest extends Scope $platformAppleIosId = $data['platformAppleIosId'] ?? ''; - $response = $this->client->call(Client::METHOD_PUT, '/projects/'.$id.'/platforms/'.$platformAppleIosId, array_merge([ + $response = $this->client->call(Client::METHOD_PUT, '/projects/' . $id . '/platforms/' . $platformAppleIosId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -1654,7 +1654,7 @@ class ProjectsConsoleClientTest extends Scope $platformAppleMacOsId = $data['platformAppleMacOsId'] ?? ''; - $response = $this->client->call(Client::METHOD_PUT, '/projects/'.$id.'/platforms/'.$platformAppleMacOsId, array_merge([ + $response = $this->client->call(Client::METHOD_PUT, '/projects/' . $id . '/platforms/' . $platformAppleMacOsId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -1675,7 +1675,7 @@ class ProjectsConsoleClientTest extends Scope $platformAppleWatchOsId = $data['platformAppleWatchOsId'] ?? ''; - $response = $this->client->call(Client::METHOD_PUT, '/projects/'.$id.'/platforms/'.$platformAppleWatchOsId, array_merge([ + $response = $this->client->call(Client::METHOD_PUT, '/projects/' . $id . '/platforms/' . $platformAppleWatchOsId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -1696,7 +1696,7 @@ class ProjectsConsoleClientTest extends Scope $platformAppleTvOsId = $data['platformAppleTvOsId'] ?? ''; - $response = $this->client->call(Client::METHOD_PUT, '/projects/'.$id.'/platforms/'.$platformAppleTvOsId, array_merge([ + $response = $this->client->call(Client::METHOD_PUT, '/projects/' . $id . '/platforms/' . $platformAppleTvOsId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -1718,7 +1718,7 @@ class ProjectsConsoleClientTest extends Scope /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_PUT, '/projects/'.$id.'/platforms/error', array_merge([ + $response = $this->client->call(Client::METHOD_PUT, '/projects/' . $id . '/platforms/error', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -1739,10 +1739,10 @@ class ProjectsConsoleClientTest extends Scope public function testDeleteProjectPlatform($data): array { $id = $data['projectId'] ?? ''; - + $platformWebId = $data['platformWebId'] ?? ''; - $response = $this->client->call(Client::METHOD_DELETE, '/projects/'.$id.'/platforms/'.$platformWebId, array_merge([ + $response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $id . '/platforms/' . $platformWebId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -1750,7 +1750,7 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(204, $response['headers']['status-code']); $this->assertEmpty($response['body']); - $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/platforms/'.$platformWebId, array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/platforms/' . $platformWebId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -1759,7 +1759,7 @@ class ProjectsConsoleClientTest extends Scope $platformFultteriOSId = $data['platformFultteriOSId'] ?? ''; - $response = $this->client->call(Client::METHOD_DELETE, '/projects/'.$id.'/platforms/'.$platformFultteriOSId, array_merge([ + $response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $id . '/platforms/' . $platformFultteriOSId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -1767,7 +1767,7 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(204, $response['headers']['status-code']); $this->assertEmpty($response['body']); - $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/platforms/'.$platformFultteriOSId, array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/platforms/' . $platformFultteriOSId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -1776,7 +1776,7 @@ class ProjectsConsoleClientTest extends Scope $platformFultterAndroidId = $data['platformFultterAndroidId'] ?? ''; - $response = $this->client->call(Client::METHOD_DELETE, '/projects/'.$id.'/platforms/'.$platformFultterAndroidId, array_merge([ + $response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $id . '/platforms/' . $platformFultterAndroidId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -1784,7 +1784,7 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(204, $response['headers']['status-code']); $this->assertEmpty($response['body']); - $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/platforms/'.$platformFultterAndroidId, array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/platforms/' . $platformFultterAndroidId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -1793,7 +1793,7 @@ class ProjectsConsoleClientTest extends Scope $platformAppleIosId = $data['platformAppleIosId'] ?? ''; - $response = $this->client->call(Client::METHOD_DELETE, '/projects/'.$id.'/platforms/'.$platformAppleIosId, array_merge([ + $response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $id . '/platforms/' . $platformAppleIosId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -1801,7 +1801,7 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(204, $response['headers']['status-code']); $this->assertEmpty($response['body']); - $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/platforms/'.$platformAppleIosId, array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/platforms/' . $platformAppleIosId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -1810,7 +1810,7 @@ class ProjectsConsoleClientTest extends Scope $platformAppleMacOsId = $data['platformAppleMacOsId'] ?? ''; - $response = $this->client->call(Client::METHOD_DELETE, '/projects/'.$id.'/platforms/'.$platformAppleMacOsId, array_merge([ + $response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $id . '/platforms/' . $platformAppleMacOsId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -1818,7 +1818,7 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(204, $response['headers']['status-code']); $this->assertEmpty($response['body']); - $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/platforms/'.$platformAppleMacOsId, array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/platforms/' . $platformAppleMacOsId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -1827,7 +1827,7 @@ class ProjectsConsoleClientTest extends Scope $platformAppleWatchOsId = $data['platformAppleWatchOsId'] ?? ''; - $response = $this->client->call(Client::METHOD_DELETE, '/projects/'.$id.'/platforms/'.$platformAppleWatchOsId, array_merge([ + $response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $id . '/platforms/' . $platformAppleWatchOsId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -1835,7 +1835,7 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(204, $response['headers']['status-code']); $this->assertEmpty($response['body']); - $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/platforms/'.$platformAppleWatchOsId, array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/platforms/' . $platformAppleWatchOsId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -1844,7 +1844,7 @@ class ProjectsConsoleClientTest extends Scope $platformAppleTvOsId = $data['platformAppleTvOsId'] ?? ''; - $response = $this->client->call(Client::METHOD_DELETE, '/projects/'.$id.'/platforms/'.$platformAppleTvOsId, array_merge([ + $response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $id . '/platforms/' . $platformAppleTvOsId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -1852,7 +1852,7 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(204, $response['headers']['status-code']); $this->assertEmpty($response['body']); - $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/platforms/'.$platformAppleTvOsId, array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/platforms/' . $platformAppleTvOsId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -1862,7 +1862,7 @@ class ProjectsConsoleClientTest extends Scope /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_DELETE, '/projects/'.$id.'/webhooks/error', array_merge([ + $response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $id . '/webhooks/error', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -1881,7 +1881,7 @@ class ProjectsConsoleClientTest extends Scope { $id = $data['projectId'] ?? ''; - $response = $this->client->call(Client::METHOD_POST, '/projects/'.$id.'/domains', array_merge([ + $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/domains', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -1895,19 +1895,19 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals('com', $response['body']['tld']); $this->assertEquals('example.com', $response['body']['registerable']); $this->assertEquals(false, $response['body']['verification']); - + $data = array_merge($data, ['domainId' => $response['body']['$id']]); /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_POST, '/projects/'.$id.'/platforms', array_merge([ + $response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/platforms', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'domain' => '123', ]); - + $this->assertEquals(400, $response['headers']['status-code']); return $data; @@ -1919,8 +1919,8 @@ class ProjectsConsoleClientTest extends Scope public function testListProjectDomain($data): array { $id = $data['projectId'] ?? ''; - - $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/domains', array_merge([ + + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/domains', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -1931,7 +1931,7 @@ class ProjectsConsoleClientTest extends Scope /** * Test for FAILURE */ - + return $data; } @@ -1943,7 +1943,7 @@ class ProjectsConsoleClientTest extends Scope $id = $data['projectId'] ?? ''; $domainId = $data['domainId'] ?? ''; - $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/domains/'.$domainId, array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/domains/' . $domainId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -1956,11 +1956,11 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals('com', $response['body']['tld']); $this->assertEquals('example.com', $response['body']['registerable']); $this->assertEquals(false, $response['body']['verification']); - + /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/domains/error', array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/domains/error', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -1978,7 +1978,7 @@ class ProjectsConsoleClientTest extends Scope $id = $data['projectId'] ?? ''; $domainId = $data['domainId'] ?? ''; - $response = $this->client->call(Client::METHOD_PATCH, '/projects/'.$id.'/domains/'.$domainId.'/verification', array_merge([ + $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/domains/' . $domainId . '/verification', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -2000,7 +2000,7 @@ class ProjectsConsoleClientTest extends Scope $id = $data['projectId'] ?? ''; $domainId = $data['domainId'] ?? ''; - $response = $this->client->call(Client::METHOD_DELETE, '/projects/'.$id.'/domains/'.$domainId, array_merge([ + $response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $id . '/domains/' . $domainId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -2008,7 +2008,7 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(204, $response['headers']['status-code']); $this->assertEmpty($response['body']); - $response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/domains/'.$domainId, array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/domains/' . $domainId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -2018,7 +2018,7 @@ class ProjectsConsoleClientTest extends Scope /** * Test for FAILURE */ - $response = $this->client->call(Client::METHOD_DELETE, '/projects/'.$id.'/domains/error', array_merge([ + $response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $id . '/domains/error', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); diff --git a/tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php b/tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php index 0b19665bf8..9d75bc159e 100644 --- a/tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php +++ b/tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php @@ -2,17 +2,10 @@ namespace Tests\E2E\Services\Realtime; -use Exception; -use SebastianBergmann\RecursionContext\InvalidArgumentException; -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\Exception as FrameworkException; use Tests\E2E\Client; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\SideConsole; -use WebSocket\BadOpcodeException; -use WebSocket\ConnectionException; -use WebSocket\TimeoutException; class RealtimeConsoleClientTest extends Scope { @@ -23,12 +16,11 @@ class RealtimeConsoleClientTest extends Scope public function testAttributes() { $user = $this->getUser(); - $session = $user['session'] ?? ''; $projectId = 'console'; $client = $this->getWebsocket(['console'], [ 'origin' => 'http://localhost', - 'cookie' => 'a_session_console='. $this->getRoot()['session'], + 'cookie' => 'a_session_console=' . $this->getRoot()['session'], ], $projectId); $response = json_decode($client->receive(), true); @@ -55,9 +47,9 @@ class RealtimeConsoleClientTest extends Scope 'permission' => 'collection' ]); - $data = ['actorsId' => $actors['body']['$id']]; + $actorsId = $actors['body']['$id']; - $name = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/attributes/string', array_merge([ + $name = $this->client->call(Client::METHOD_POST, '/database/collections/' . $actorsId . '/attributes/string', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -66,6 +58,8 @@ class RealtimeConsoleClientTest extends Scope 'required' => true, ]); + $attributeKey = $name['body']['key']; + $this->assertEquals($name['headers']['status-code'], 201); $this->assertEquals($name['body']['key'], 'name'); $this->assertEquals($name['body']['type'], 'string'); @@ -81,7 +75,16 @@ class RealtimeConsoleClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertCount(1, $response['data']['channels']); $this->assertContains('console', $response['data']['channels']); - $this->assertEquals('database.attributes.create', $response['data']['event']); + $this->assertContains("collections.{$actorsId}.attributes.{$actorsId}_{$attributeKey}.create", $response['data']['events']); + $this->assertContains("collections.{$actorsId}.attributes.*.create", $response['data']['events']); + $this->assertContains("collections.{$actorsId}.attributes.{$actorsId}_{$attributeKey}", $response['data']['events']); + $this->assertContains("collections.{$actorsId}.attributes.*", $response['data']['events']); + $this->assertContains("collections.{$actorsId}", $response['data']['events']); + $this->assertContains("collections.*.attributes.{$actorsId}_{$attributeKey}.create", $response['data']['events']); + $this->assertContains("collections.*.attributes.*.create", $response['data']['events']); + $this->assertContains("collections.*.attributes.{$actorsId}_{$attributeKey}", $response['data']['events']); + $this->assertContains("collections.*.attributes.*", $response['data']['events']); + $this->assertContains("collections.*", $response['data']['events']); $this->assertNotEmpty($response['data']['payload']); $this->assertEquals('processing', $response['data']['payload']['status']); @@ -94,12 +97,23 @@ class RealtimeConsoleClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertCount(1, $response['data']['channels']); $this->assertContains('console', $response['data']['channels']); - $this->assertEquals('database.attributes.update', $response['data']['event']); + $this->assertContains("collections.{$actorsId}.attributes.{$actorsId}_{$attributeKey}.update", $response['data']['events']); + $this->assertContains("collections.{$actorsId}.attributes.*.update", $response['data']['events']); + $this->assertContains("collections.{$actorsId}.attributes.{$actorsId}_{$attributeKey}", $response['data']['events']); + $this->assertContains("collections.{$actorsId}.attributes.*", $response['data']['events']); + $this->assertContains("collections.{$actorsId}", $response['data']['events']); + $this->assertContains("collections.*.attributes.{$actorsId}_{$attributeKey}.update", $response['data']['events']); + $this->assertContains("collections.*.attributes.*.update", $response['data']['events']); + $this->assertContains("collections.*.attributes.{$actorsId}_{$attributeKey}", $response['data']['events']); + $this->assertContains("collections.*.attributes.*", $response['data']['events']); + $this->assertContains("collections.*", $response['data']['events']); $this->assertNotEmpty($response['data']['payload']); $this->assertEquals('available', $response['data']['payload']['status']); $client->close(); + $data = ['actorsId' => $actorsId]; + return $data; } @@ -108,13 +122,11 @@ class RealtimeConsoleClientTest extends Scope */ public function testIndexes(array $data) { - $user = $this->getUser(); - $session = $user['session'] ?? ''; $projectId = 'console'; - + $actorsId = $data['actorsId']; $client = $this->getWebsocket(['console'], [ 'origin' => 'http://localhost', - 'cookie' => 'a_session_console='. $this->getRoot()['session'], + 'cookie' => 'a_session_console=' . $this->getRoot()['session'], ], $projectId); $response = json_decode($client->receive(), true); @@ -130,7 +142,7 @@ class RealtimeConsoleClientTest extends Scope /** * Test Indexes */ - $index = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/indexes', array_merge([ + $index = $this->client->call(Client::METHOD_POST, '/database/collections/' . $actorsId . '/indexes', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -142,6 +154,7 @@ class RealtimeConsoleClientTest extends Scope ]); $this->assertEquals($index['headers']['status-code'], 201); + $indexKey = $index['body']['key']; $response = json_decode($client->receive(), true); @@ -152,7 +165,16 @@ class RealtimeConsoleClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertCount(1, $response['data']['channels']); $this->assertContains('console', $response['data']['channels']); - $this->assertEquals('database.indexes.create', $response['data']['event']); + $this->assertContains("collections.{$actorsId}.indexes.{$actorsId}_{$indexKey}.create", $response['data']['events']); + $this->assertContains("collections.{$actorsId}.indexes.*.create", $response['data']['events']); + $this->assertContains("collections.{$actorsId}.indexes.{$actorsId}_{$indexKey}", $response['data']['events']); + $this->assertContains("collections.{$actorsId}.indexes.*", $response['data']['events']); + $this->assertContains("collections.{$actorsId}", $response['data']['events']); + $this->assertContains("collections.*.indexes.{$actorsId}_{$indexKey}.create", $response['data']['events']); + $this->assertContains("collections.*.indexes.*.create", $response['data']['events']); + $this->assertContains("collections.*.indexes.{$actorsId}_{$indexKey}", $response['data']['events']); + $this->assertContains("collections.*.indexes.*", $response['data']['events']); + $this->assertContains("collections.*", $response['data']['events']); $this->assertNotEmpty($response['data']['payload']); $this->assertEquals('processing', $response['data']['payload']['status']); @@ -165,7 +187,16 @@ class RealtimeConsoleClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertCount(1, $response['data']['channels']); $this->assertContains('console', $response['data']['channels']); - $this->assertEquals('database.indexes.update', $response['data']['event']); + $this->assertContains("collections.{$actorsId}.indexes.{$actorsId}_{$indexKey}.update", $response['data']['events']); + $this->assertContains("collections.{$actorsId}.indexes.*.update", $response['data']['events']); + $this->assertContains("collections.{$actorsId}.indexes.{$actorsId}_{$indexKey}", $response['data']['events']); + $this->assertContains("collections.{$actorsId}.indexes.*", $response['data']['events']); + $this->assertContains("collections.{$actorsId}", $response['data']['events']); + $this->assertContains("collections.*.indexes.{$actorsId}_{$indexKey}.update", $response['data']['events']); + $this->assertContains("collections.*.indexes.*.update", $response['data']['events']); + $this->assertContains("collections.*.indexes.{$actorsId}_{$indexKey}", $response['data']['events']); + $this->assertContains("collections.*.indexes.*", $response['data']['events']); + $this->assertContains("collections.*", $response['data']['events']); $this->assertNotEmpty($response['data']['payload']); $this->assertEquals('available', $response['data']['payload']['status']); @@ -179,13 +210,12 @@ class RealtimeConsoleClientTest extends Scope */ public function testDeleteIndex(array $data) { - $user = $this->getUser(); - $session = $user['session'] ?? ''; + $actorsId = $data['actorsId']; $projectId = 'console'; $client = $this->getWebsocket(['console'], [ 'origin' => 'http://localhost', - 'cookie' => 'a_session_console='. $this->getRoot()['session'], + 'cookie' => 'a_session_console=' . $this->getRoot()['session'], ], $projectId); $response = json_decode($client->receive(), true); @@ -201,13 +231,13 @@ class RealtimeConsoleClientTest extends Scope /** * Test Delete Index */ - $attribute = $this->client->call(Client::METHOD_DELETE, '/database/collections/' . $data['actorsId'] . '/indexes/key_name', array_merge([ + $attribute = $this->client->call(Client::METHOD_DELETE, '/database/collections/' . $actorsId . '/indexes/key_name', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); $this->assertEquals($attribute['headers']['status-code'], 204); - + $indexKey = 'key_name'; $response = json_decode($client->receive(), true); $this->assertArrayHasKey('type', $response); @@ -217,7 +247,16 @@ class RealtimeConsoleClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertCount(1, $response['data']['channels']); $this->assertContains('console', $response['data']['channels']); - $this->assertEquals('database.indexes.delete', $response['data']['event']); + $this->assertContains("collections.{$actorsId}.indexes.{$actorsId}_{$indexKey}.delete", $response['data']['events']); + $this->assertContains("collections.{$actorsId}.indexes.*.delete", $response['data']['events']); + $this->assertContains("collections.{$actorsId}.indexes.{$actorsId}_{$indexKey}", $response['data']['events']); + $this->assertContains("collections.{$actorsId}.indexes.*", $response['data']['events']); + $this->assertContains("collections.{$actorsId}", $response['data']['events']); + $this->assertContains("collections.*.indexes.{$actorsId}_{$indexKey}.delete", $response['data']['events']); + $this->assertContains("collections.*.indexes.*.delete", $response['data']['events']); + $this->assertContains("collections.*.indexes.{$actorsId}_{$indexKey}", $response['data']['events']); + $this->assertContains("collections.*.indexes.*", $response['data']['events']); + $this->assertContains("collections.*", $response['data']['events']); $this->assertNotEmpty($response['data']['payload']); $client->close(); @@ -230,13 +269,12 @@ class RealtimeConsoleClientTest extends Scope */ public function testDeleteAttribute(array $data) { - $user = $this->getUser(); - $session = $user['session'] ?? ''; + $actorsId = $data['actorsId']; $projectId = 'console'; $client = $this->getWebsocket(['console'], [ 'origin' => 'http://localhost', - 'cookie' => 'a_session_console='. $this->getRoot()['session'], + 'cookie' => 'a_session_console=' . $this->getRoot()['session'], ], $projectId); $response = json_decode($client->receive(), true); @@ -258,7 +296,7 @@ class RealtimeConsoleClientTest extends Scope ], $this->getHeaders())); $this->assertEquals($attribute['headers']['status-code'], 204); - + $attributeKey = 'name'; $response = json_decode($client->receive(), true); $this->assertArrayHasKey('type', $response); @@ -268,9 +306,18 @@ class RealtimeConsoleClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertCount(1, $response['data']['channels']); $this->assertContains('console', $response['data']['channels']); - $this->assertEquals('database.attributes.delete', $response['data']['event']); + $this->assertContains("collections.{$actorsId}.attributes.{$actorsId}_{$attributeKey}.delete", $response['data']['events']); + $this->assertContains("collections.{$actorsId}.attributes.*.delete", $response['data']['events']); + $this->assertContains("collections.{$actorsId}.attributes.{$actorsId}_{$attributeKey}", $response['data']['events']); + $this->assertContains("collections.{$actorsId}.attributes.*", $response['data']['events']); + $this->assertContains("collections.{$actorsId}", $response['data']['events']); + $this->assertContains("collections.*.attributes.{$actorsId}_{$attributeKey}.delete", $response['data']['events']); + $this->assertContains("collections.*.attributes.*.delete", $response['data']['events']); + $this->assertContains("collections.*.attributes.{$actorsId}_{$attributeKey}", $response['data']['events']); + $this->assertContains("collections.*.attributes.*", $response['data']['events']); + $this->assertContains("collections.*", $response['data']['events']); $this->assertNotEmpty($response['data']['payload']); $client->close(); } -} \ No newline at end of file +} diff --git a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php index 194ccebdaa..017792a61f 100644 --- a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php +++ b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php @@ -25,7 +25,7 @@ class RealtimeCustomClientTest extends Scope $headers = [ 'origin' => 'http://localhost', - 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session + 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session ]; $client = $this->getWebsocket(['documents'], $headers); @@ -240,7 +240,7 @@ class RealtimeCustomClientTest extends Scope $client = $this->getWebsocket(['account'], [ 'origin' => 'http://localhost', - 'cookie' => 'a_session_'.$projectId.'=' . $session + 'cookie' => 'a_session_' . $projectId . '=' . $session ]); $response = json_decode($client->receive(), true); @@ -278,7 +278,12 @@ class RealtimeCustomClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertContains('account', $response['data']['channels']); $this->assertContains('account.' . $userId, $response['data']['channels']); - $this->assertEquals("users.{$userId}.update.name", $response['data']['event']); + $this->assertContains("users.{$userId}.update.name", $response['data']['events']); + $this->assertContains("users.{$userId}.update", $response['data']['events']); + $this->assertContains("users.{$userId}", $response['data']['events']); + $this->assertContains("users.*.update.name", $response['data']['events']); + $this->assertContains("users.*.update", $response['data']['events']); + $this->assertContains("users.*", $response['data']['events']); $this->assertNotEmpty($response['data']['payload']); $this->assertEquals($name, $response['data']['payload']['name']); @@ -291,7 +296,7 @@ class RealtimeCustomClientTest extends Scope 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $projectId, - 'cookie' => 'a_session_'.$projectId.'=' . $session, + 'cookie' => 'a_session_' . $projectId . '=' . $session, ]), [ 'password' => 'new-password', 'oldPassword' => 'password', @@ -307,7 +312,12 @@ class RealtimeCustomClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertContains('account', $response['data']['channels']); $this->assertContains('account.' . $userId, $response['data']['channels']); - $this->assertEquals("users.{$userId}.update.password", $response['data']['event']); + $this->assertContains("users.{$userId}.update.password", $response['data']['events']); + $this->assertContains("users.{$userId}.update", $response['data']['events']); + $this->assertContains("users.{$userId}", $response['data']['events']); + $this->assertContains("users.*.update.password", $response['data']['events']); + $this->assertContains("users.*.update", $response['data']['events']); + $this->assertContains("users.*", $response['data']['events']); $this->assertNotEmpty($response['data']['payload']); $this->assertEquals($name, $response['data']['payload']['name']); @@ -319,7 +329,7 @@ class RealtimeCustomClientTest extends Scope 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $projectId, - 'cookie' => 'a_session_'.$projectId.'=' . $session, + 'cookie' => 'a_session_' . $projectId . '=' . $session, ]), [ 'email' => 'torsten@appwrite.io', 'password' => 'new-password', @@ -335,9 +345,13 @@ class RealtimeCustomClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertContains('account', $response['data']['channels']); $this->assertContains('account.' . $userId, $response['data']['channels']); - $this->assertEquals("users.{$userId}.update.email", $response['data']['event']); + $this->assertContains("users.{$userId}.update.email", $response['data']['events']); + $this->assertContains("users.{$userId}.update", $response['data']['events']); + $this->assertContains("users.{$userId}", $response['data']['events']); + $this->assertContains("users.*.update.email", $response['data']['events']); + $this->assertContains("users.*.update", $response['data']['events']); + $this->assertContains("users.*", $response['data']['events']); $this->assertNotEmpty($response['data']['payload']); - $this->assertEquals('torsten@appwrite.io', $response['data']['payload']['email']); /** @@ -347,11 +361,11 @@ class RealtimeCustomClientTest extends Scope 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $projectId, - 'cookie' => 'a_session_'.$projectId.'=' . $session, + 'cookie' => 'a_session_' . $projectId . '=' . $session, ]), [ 'url' => 'http://localhost/verification', ]); - + $verificationId = $verification['body']['$id']; $response = json_decode($client->receive(), true); $this->assertArrayHasKey('type', $response); @@ -362,7 +376,16 @@ class RealtimeCustomClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertContains('account', $response['data']['channels']); $this->assertContains('account.' . $userId, $response['data']['channels']); - $this->assertEquals("users.{$userId}.verification.{$verification['body']['$id']}.create", $response['data']['event']); + $this->assertContains("users.{$userId}.verification.{$verificationId}.create", $response['data']['events']); + $this->assertContains("users.{$userId}.verification.{$verificationId}", $response['data']['events']); + $this->assertContains("users.{$userId}.verification.*.create", $response['data']['events']); + $this->assertContains("users.{$userId}.verification.*", $response['data']['events']); + $this->assertContains("users.{$userId}", $response['data']['events']); + $this->assertContains("users.*.verification.{$verificationId}.create", $response['data']['events']); + $this->assertContains("users.*.verification.{$verificationId}", $response['data']['events']); + $this->assertContains("users.*.verification.*.create", $response['data']['events']); + $this->assertContains("users.*.verification.*", $response['data']['events']); + $this->assertContains("users.*", $response['data']['events']); $lastEmail = $this->getLastEmail(); $verification = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 256); @@ -374,7 +397,7 @@ class RealtimeCustomClientTest extends Scope 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $projectId, - 'cookie' => 'a_session_'.$projectId.'=' . $session, + 'cookie' => 'a_session_' . $projectId . '=' . $session, ]), [ 'userId' => $userId, 'secret' => $verification, @@ -390,8 +413,16 @@ class RealtimeCustomClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertContains('account', $response['data']['channels']); $this->assertContains('account.' . $userId, $response['data']['channels']); - $this->assertEquals("users.{$userId}.verification.{$verification['body']['$id']}.update", $response['data']['event']); - + $this->assertContains("users.{$userId}.verification.{$verificationId}.update", $response['data']['events']); + $this->assertContains("users.{$userId}.verification.{$verificationId}", $response['data']['events']); + $this->assertContains("users.{$userId}.verification.*.update", $response['data']['events']); + $this->assertContains("users.{$userId}.verification.*", $response['data']['events']); + $this->assertContains("users.{$userId}", $response['data']['events']); + $this->assertContains("users.*.verification.{$verificationId}.update", $response['data']['events']); + $this->assertContains("users.*.verification.{$verificationId}", $response['data']['events']); + $this->assertContains("users.*.verification.*.update", $response['data']['events']); + $this->assertContains("users.*.verification.*", $response['data']['events']); + $this->assertContains("users.*", $response['data']['events']); /** * Test Acoount Prefs Update */ @@ -399,7 +430,7 @@ class RealtimeCustomClientTest extends Scope 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $projectId, - 'cookie' => 'a_session_'.$projectId.'=' . $session, + 'cookie' => 'a_session_' . $projectId . '=' . $session, ]), [ 'prefs' => [ 'prefKey1' => 'prefValue1', @@ -417,7 +448,12 @@ class RealtimeCustomClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertContains('account', $response['data']['channels']); $this->assertContains('account.' . $userId, $response['data']['channels']); - $this->assertEquals("users.{$userId}.update.prefs", $response['data']['event']); + $this->assertContains("users.{$userId}.update.prefs", $response['data']['events']); + $this->assertContains("users.{$userId}.update", $response['data']['events']); + $this->assertContains("users.{$userId}", $response['data']['events']); + $this->assertContains("users.*.update.prefs", $response['data']['events']); + $this->assertContains("users.*.update", $response['data']['events']); + $this->assertContains("users.*", $response['data']['events']); $this->assertNotEmpty($response['data']['payload']); /** @@ -432,7 +468,7 @@ class RealtimeCustomClientTest extends Scope 'password' => 'new-password', ]); - $sessionNew = $this->client->parseCookie((string)$response['headers']['set-cookie'])['a_session_'.$projectId]; + $sessionNew = $this->client->parseCookie((string)$response['headers']['set-cookie'])['a_session_' . $projectId]; $sessionNewId = $response['body']['$id']; $response = json_decode($client->receive(), true); @@ -445,17 +481,26 @@ class RealtimeCustomClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertContains('account', $response['data']['channels']); $this->assertContains('account.' . $userId, $response['data']['channels']); - $this->assertEquals("users.{$userId}.sessions.{$sessionNewId}.create", $response['data']['event']); + $this->assertContains("users.{$userId}.sessions.{$sessionNewId}.create", $response['data']['events']); + $this->assertContains("users.{$userId}.sessions.{$sessionNewId}", $response['data']['events']); + $this->assertContains("users.{$userId}.sessions.*.create", $response['data']['events']); + $this->assertContains("users.{$userId}.sessions.*", $response['data']['events']); + $this->assertContains("users.{$userId}", $response['data']['events']); + $this->assertContains("users.*.sessions.{$sessionNewId}.create", $response['data']['events']); + $this->assertContains("users.*.sessions.{$sessionNewId}", $response['data']['events']); + $this->assertContains("users.*.sessions.*.create", $response['data']['events']); + $this->assertContains("users.*.sessions.*", $response['data']['events']); + $this->assertContains("users.*", $response['data']['events']); $this->assertNotEmpty($response['data']['payload']); /** * Test Account Session Delete */ - $this->client->call(Client::METHOD_DELETE, '/account/sessions/'.$sessionNewId, array_merge([ + $this->client->call(Client::METHOD_DELETE, '/account/sessions/' . $sessionNewId, array_merge([ 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $projectId, - 'cookie' => 'a_session_'.$projectId.'=' . $sessionNew, + 'cookie' => 'a_session_' . $projectId . '=' . $sessionNew, ])); $response = json_decode($client->receive(), true); @@ -468,7 +513,16 @@ class RealtimeCustomClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertContains('account', $response['data']['channels']); $this->assertContains('account.' . $userId, $response['data']['channels']); - $this->assertEquals("users.{$userId}.sessions.{$sessionNewId}.delete", $response['data']['event']); + $this->assertContains("users.{$userId}.sessions.{$sessionNewId}.delete", $response['data']['events']); + $this->assertContains("users.{$userId}.sessions.{$sessionNewId}", $response['data']['events']); + $this->assertContains("users.{$userId}.sessions.*.delete", $response['data']['events']); + $this->assertContains("users.{$userId}.sessions.*", $response['data']['events']); + $this->assertContains("users.{$userId}", $response['data']['events']); + $this->assertContains("users.*.sessions.{$sessionNewId}.delete", $response['data']['events']); + $this->assertContains("users.*.sessions.{$sessionNewId}", $response['data']['events']); + $this->assertContains("users.*.sessions.*.delete", $response['data']['events']); + $this->assertContains("users.*.sessions.*", $response['data']['events']); + $this->assertContains("users.*", $response['data']['events']); $this->assertNotEmpty($response['data']['payload']); /** @@ -496,7 +550,16 @@ class RealtimeCustomClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertContains('account', $response['data']['channels']); $this->assertContains('account.' . $userId, $response['data']['channels']); - $this->assertEquals("users.{$userId}.recovery.{$recoveryId}.create", $response['data']['event']); + $this->assertContains("users.{$userId}.recovery.{$recoveryId}.create", $response['data']['events']); + $this->assertContains("users.{$userId}.recovery.{$recoveryId}", $response['data']['events']); + $this->assertContains("users.{$userId}.recovery.*.create", $response['data']['events']); + $this->assertContains("users.{$userId}.recovery.*", $response['data']['events']); + $this->assertContains("users.{$userId}", $response['data']['events']); + $this->assertContains("users.*.recovery.{$recoveryId}.create", $response['data']['events']); + $this->assertContains("users.*.recovery.{$recoveryId}", $response['data']['events']); + $this->assertContains("users.*.recovery.*.create", $response['data']['events']); + $this->assertContains("users.*.recovery.*", $response['data']['events']); + $this->assertContains("users.*", $response['data']['events']); $this->assertNotEmpty($response['data']['payload']); $response = $this->client->call(Client::METHOD_PUT, '/account/recovery', array_merge([ @@ -520,7 +583,16 @@ class RealtimeCustomClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertContains('account', $response['data']['channels']); $this->assertContains('account.' . $userId, $response['data']['channels']); - $this->assertEquals("users.{$userId}.recovery.{$recoveryId}.update", $response['data']['event']); + $this->assertContains("users.{$userId}.recovery.{$recoveryId}.update", $response['data']['events']); + $this->assertContains("users.{$userId}.recovery.{$recoveryId}", $response['data']['events']); + $this->assertContains("users.{$userId}.recovery.*.update", $response['data']['events']); + $this->assertContains("users.{$userId}.recovery.*", $response['data']['events']); + $this->assertContains("users.{$userId}", $response['data']['events']); + $this->assertContains("users.*.recovery.{$recoveryId}.update", $response['data']['events']); + $this->assertContains("users.*.recovery.{$recoveryId}", $response['data']['events']); + $this->assertContains("users.*.recovery.*.update", $response['data']['events']); + $this->assertContains("users.*.recovery.*", $response['data']['events']); + $this->assertContains("users.*", $response['data']['events']); $this->assertNotEmpty($response['data']['payload']); $client->close(); @@ -534,7 +606,7 @@ class RealtimeCustomClientTest extends Scope $client = $this->getWebsocket(['documents', 'collections'], [ 'origin' => 'http://localhost', - 'cookie' => 'a_session_'.$projectId.'=' . $session + 'cookie' => 'a_session_' . $projectId . '=' . $session ]); $response = json_decode($client->receive(), true); @@ -564,9 +636,9 @@ class RealtimeCustomClientTest extends Scope 'permission' => 'document' ]); - $data = ['actorsId' => $actors['body']['$id']]; + $actorsId = $actors['body']['$id']; - $name = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/attributes/string', array_merge([ + $name = $this->client->call(Client::METHOD_POST, '/database/collections/' . $actorsId . '/attributes/string', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -587,7 +659,7 @@ class RealtimeCustomClientTest extends Scope /** * Test Document Create */ - $document = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/documents', array_merge([ + $document = $this->client->call(Client::METHOD_POST, '/database/collections/' . $actorsId . '/documents', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -601,6 +673,8 @@ class RealtimeCustomClientTest extends Scope $response = json_decode($client->receive(), true); + $documentId = $document['body']['$id']; + $this->assertArrayHasKey('type', $response); $this->assertArrayHasKey('data', $response); $this->assertEquals('event', $response['type']); @@ -608,18 +682,25 @@ class RealtimeCustomClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertCount(3, $response['data']['channels']); $this->assertContains('documents', $response['data']['channels']); - $this->assertContains('collections.' . $data['actorsId'] . '.documents.' . $document['body']['$id'], $response['data']['channels']); - $this->assertContains('collections.' . $actors['body']['$id'] . '.documents', $response['data']['channels']); - $this->assertEquals("collections.{$data['actorsId']}.documents.{$document['body']['$id']}.create", $response['data']['event']); + $this->assertContains('collections.' . $actorsId . '.documents.' . $documentId, $response['data']['channels']); + $this->assertContains('collections.' . $actorsId . '.documents', $response['data']['channels']); + $this->assertContains("collections.{$actorsId}.documents.{$documentId}.create", $response['data']['events']); + $this->assertContains("collections.{$actorsId}.documents.{$documentId}", $response['data']['events']); + $this->assertContains("collections.{$actorsId}.documents.*.create", $response['data']['events']); + $this->assertContains("collections.{$actorsId}.documents.*", $response['data']['events']); + $this->assertContains("collections.{$actorsId}", $response['data']['events']); + $this->assertContains("collections.*.documents.{$documentId}.create", $response['data']['events']); + $this->assertContains("collections.*.documents.{$documentId}", $response['data']['events']); + $this->assertContains("collections.*.documents.*.create", $response['data']['events']); + $this->assertContains("collections.*.documents.*", $response['data']['events']); + $this->assertContains("collections.*", $response['data']['events']); $this->assertNotEmpty($response['data']['payload']); $this->assertEquals($response['data']['payload']['name'], 'Chris Evans'); - $data['documentId'] = $document['body']['$id']; - /** * Test Document Update */ - $document = $this->client->call(Client::METHOD_PATCH, '/database/collections/' . $data['actorsId'] . '/documents/' . $data['documentId'], array_merge([ + $document = $this->client->call(Client::METHOD_PATCH, '/database/collections/' . $actorsId . '/documents/' . $documentId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -640,9 +721,18 @@ class RealtimeCustomClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertCount(3, $response['data']['channels']); $this->assertContains('documents', $response['data']['channels']); - $this->assertContains('collections.' . $data['actorsId'] . '.documents.' . $data['documentId'], $response['data']['channels']); - $this->assertContains('collections.' . $data['actorsId'] . '.documents', $response['data']['channels']); - $this->assertEquals("collections.{$data['actorsId']}.documents.{$data['documentId']}.update", $response['data']['event']); + $this->assertContains("collections.{$actorsId}.documents.{$documentId}", $response['data']['channels']); + $this->assertContains("collections.{$actorsId}.documents", $response['data']['channels']); + $this->assertContains("collections.{$actorsId}.documents.{$documentId}.update", $response['data']['events']); + $this->assertContains("collections.{$actorsId}.documents.{$documentId}", $response['data']['events']); + $this->assertContains("collections.{$actorsId}.documents.*.update", $response['data']['events']); + $this->assertContains("collections.{$actorsId}.documents.*", $response['data']['events']); + $this->assertContains("collections.{$actorsId}", $response['data']['events']); + $this->assertContains("collections.*.documents.{$documentId}.update", $response['data']['events']); + $this->assertContains("collections.*.documents.{$documentId}", $response['data']['events']); + $this->assertContains("collections.*.documents.*.update", $response['data']['events']); + $this->assertContains("collections.*.documents.*", $response['data']['events']); + $this->assertContains("collections.*", $response['data']['events']); $this->assertNotEmpty($response['data']['payload']); $this->assertEquals($response['data']['payload']['name'], 'Chris Evans 2'); @@ -650,7 +740,7 @@ class RealtimeCustomClientTest extends Scope /** * Test Document Delete */ - $document = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/documents', array_merge([ + $document = $this->client->call(Client::METHOD_POST, '/database/collections/' . $actorsId . '/documents', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -664,7 +754,9 @@ class RealtimeCustomClientTest extends Scope $client->receive(); - $this->client->call(Client::METHOD_DELETE, '/database/collections/' . $data['actorsId'] . '/documents/' . $document['body']['$id'], array_merge([ + $documentId = $document['body']['$id']; + + $this->client->call(Client::METHOD_DELETE, '/database/collections/' . $actorsId . '/documents/' . $documentId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -678,9 +770,18 @@ class RealtimeCustomClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertCount(3, $response['data']['channels']); $this->assertContains('documents', $response['data']['channels']); - $this->assertContains('collections.' . $data['actorsId'] . '.documents.' . $document['body']['$id'], $response['data']['channels']); - $this->assertContains('collections.' . $data['actorsId'] . '.documents', $response['data']['channels']); - $this->assertEquals("collections.{$data['actorsId']}.documents.{$document['body']['$id']}.delete", $response['data']['event']); + $this->assertContains("collections.{$actorsId}.documents.{$documentId}", $response['data']['channels']); + $this->assertContains("collections.{$actorsId}.documents", $response['data']['channels']); + $this->assertContains("collections.{$actorsId}.documents.{$documentId}.delete", $response['data']['events']); + $this->assertContains("collections.{$actorsId}.documents.{$documentId}", $response['data']['events']); + $this->assertContains("collections.{$actorsId}.documents.*.delete", $response['data']['events']); + $this->assertContains("collections.{$actorsId}.documents.*", $response['data']['events']); + $this->assertContains("collections.{$actorsId}", $response['data']['events']); + $this->assertContains("collections.*.documents.{$documentId}.delete", $response['data']['events']); + $this->assertContains("collections.*.documents.{$documentId}", $response['data']['events']); + $this->assertContains("collections.*.documents.*.delete", $response['data']['events']); + $this->assertContains("collections.*.documents.*", $response['data']['events']); + $this->assertContains("collections.*", $response['data']['events']); $this->assertNotEmpty($response['data']['payload']); $this->assertEquals($response['data']['payload']['name'], 'Bradley Cooper'); @@ -695,7 +796,7 @@ class RealtimeCustomClientTest extends Scope $client = $this->getWebsocket(['documents', 'collections'], [ 'origin' => 'http://localhost', - 'cookie' => 'a_session_'.$projectId.'=' . $session + 'cookie' => 'a_session_' . $projectId . '=' . $session ]); $response = json_decode($client->receive(), true); @@ -725,9 +826,9 @@ class RealtimeCustomClientTest extends Scope 'permission' => 'collection' ]); - $data = ['actorsId' => $actors['body']['$id']]; + $actorsId = $actors['body']['$id']; - $name = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/attributes/string', array_merge([ + $name = $this->client->call(Client::METHOD_POST, '/database/collections/' . $actorsId . '/attributes/string', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -748,7 +849,7 @@ class RealtimeCustomClientTest extends Scope /** * Test Document Create */ - $document = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/documents', array_merge([ + $document = $this->client->call(Client::METHOD_POST, '/database/collections/' . $actorsId . '/documents', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -760,6 +861,8 @@ class RealtimeCustomClientTest extends Scope 'write' => [], ]); + $documentId = $document['body']['$id']; + $response = json_decode($client->receive(), true); $this->assertArrayHasKey('type', $response); @@ -769,18 +872,25 @@ class RealtimeCustomClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertCount(3, $response['data']['channels']); $this->assertContains('documents', $response['data']['channels']); - $this->assertContains('collections.' . $data['actorsId'] . '.documents.' . $document['body']['$id'], $response['data']['channels']); - $this->assertContains('collections.' . $actors['body']['$id'] . '.documents', $response['data']['channels']); - $this->assertEquals("collections.{$data['actorsId']}.documents.{$document['body']['$id']}.create", $response['data']['event']); + $this->assertContains("collections.{$actorsId}.documents.{$documentId}", $response['data']['channels']); + $this->assertContains("collections.{$actorsId}.documents", $response['data']['channels']); + $this->assertContains("collections.{$actorsId}.documents.{$documentId}.create", $response['data']['events']); + $this->assertContains("collections.{$actorsId}.documents.{$documentId}", $response['data']['events']); + $this->assertContains("collections.{$actorsId}.documents.*.create", $response['data']['events']); + $this->assertContains("collections.{$actorsId}.documents.*", $response['data']['events']); + $this->assertContains("collections.{$actorsId}", $response['data']['events']); + $this->assertContains("collections.*.documents.{$documentId}.create", $response['data']['events']); + $this->assertContains("collections.*.documents.{$documentId}", $response['data']['events']); + $this->assertContains("collections.*.documents.*.create", $response['data']['events']); + $this->assertContains("collections.*.documents.*", $response['data']['events']); + $this->assertContains("collections.*", $response['data']['events']); $this->assertNotEmpty($response['data']['payload']); $this->assertEquals($response['data']['payload']['name'], 'Chris Evans'); - $data['documentId'] = $document['body']['$id']; - /** * Test Document Update */ - $document = $this->client->call(Client::METHOD_PATCH, '/database/collections/' . $data['actorsId'] . '/documents/' . $data['documentId'], array_merge([ + $document = $this->client->call(Client::METHOD_PATCH, '/database/collections/' . $actorsId . '/documents/' . $documentId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -800,9 +910,18 @@ class RealtimeCustomClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertCount(3, $response['data']['channels']); $this->assertContains('documents', $response['data']['channels']); - $this->assertContains('collections.' . $data['actorsId'] . '.documents.' . $data['documentId'], $response['data']['channels']); - $this->assertContains('collections.' . $data['actorsId'] . '.documents', $response['data']['channels']); - $this->assertEquals("collections.{$data['actorsId']}.documents.{$document['body']['$id']}.update", $response['data']['event']); + $this->assertContains("collections.{$actorsId}.documents.{$documentId}", $response['data']['channels']); + $this->assertContains("collections.{$actorsId}.documents", $response['data']['channels']); + $this->assertContains("collections.{$actorsId}.documents.{$documentId}.update", $response['data']['events']); + $this->assertContains("collections.{$actorsId}.documents.{$documentId}", $response['data']['events']); + $this->assertContains("collections.{$actorsId}.documents.*.update", $response['data']['events']); + $this->assertContains("collections.{$actorsId}.documents.*", $response['data']['events']); + $this->assertContains("collections.{$actorsId}", $response['data']['events']); + $this->assertContains("collections.*.documents.{$documentId}.update", $response['data']['events']); + $this->assertContains("collections.*.documents.{$documentId}", $response['data']['events']); + $this->assertContains("collections.*.documents.*.update", $response['data']['events']); + $this->assertContains("collections.*.documents.*", $response['data']['events']); + $this->assertContains("collections.*", $response['data']['events']); $this->assertNotEmpty($response['data']['payload']); $this->assertEquals($response['data']['payload']['name'], 'Chris Evans 2'); @@ -810,7 +929,7 @@ class RealtimeCustomClientTest extends Scope /** * Test Document Delete */ - $document = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/documents', array_merge([ + $document = $this->client->call(Client::METHOD_POST, '/database/collections/' . $actorsId . '/documents', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -822,9 +941,11 @@ class RealtimeCustomClientTest extends Scope 'write' => [], ]); + $documentId = $document['body']['$id']; + $client->receive(); - $this->client->call(Client::METHOD_DELETE, '/database/collections/' . $data['actorsId'] . '/documents/' . $document['body']['$id'], array_merge([ + $this->client->call(Client::METHOD_DELETE, '/database/collections/' . $actorsId . '/documents/' . $documentId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -838,9 +959,18 @@ class RealtimeCustomClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertCount(3, $response['data']['channels']); $this->assertContains('documents', $response['data']['channels']); - $this->assertContains('collections.' . $data['actorsId'] . '.documents.' . $document['body']['$id'], $response['data']['channels']); - $this->assertContains('collections.' . $data['actorsId'] . '.documents', $response['data']['channels']); - $this->assertEquals("collections.{$data['actorsId']}.documents.{$document['body']['$id']}.delete", $response['data']['event']); + $this->assertContains("collections.{$actorsId}.documents.{$documentId}", $response['data']['channels']); + $this->assertContains("collections.{$actorsId}.documents", $response['data']['channels']); + $this->assertContains("collections.{$actorsId}.documents.{$documentId}.delete", $response['data']['events']); + $this->assertContains("collections.{$actorsId}.documents.{$documentId}", $response['data']['events']); + $this->assertContains("collections.{$actorsId}.documents.*.delete", $response['data']['events']); + $this->assertContains("collections.{$actorsId}.documents.*", $response['data']['events']); + $this->assertContains("collections.{$actorsId}", $response['data']['events']); + $this->assertContains("collections.*.documents.{$documentId}.delete", $response['data']['events']); + $this->assertContains("collections.*.documents.{$documentId}", $response['data']['events']); + $this->assertContains("collections.*.documents.*.delete", $response['data']['events']); + $this->assertContains("collections.*.documents.*", $response['data']['events']); + $this->assertContains("collections.*", $response['data']['events']); $this->assertNotEmpty($response['data']['payload']); $this->assertEquals($response['data']['payload']['name'], 'Bradley Cooper'); @@ -855,7 +985,7 @@ class RealtimeCustomClientTest extends Scope $client = $this->getWebsocket(['files'], [ 'origin' => 'http://localhost', - 'cookie' => 'a_session_'.$projectId.'=' . $session + 'cookie' => 'a_session_' . $projectId . '=' . $session ]); $response = json_decode($client->receive(), true); @@ -883,11 +1013,12 @@ class RealtimeCustomClientTest extends Scope 'permission' => 'bucket' ]); - $data = ['bucketId' => $bucket1['body']['$id']]; + $bucketId = $bucket1['body']['$id']; + /** * Test File Create */ - $file = $this->client->call(Client::METHOD_POST, '/storage/buckets/' . $data['bucketId'] . '/files', array_merge([ + $file = $this->client->call(Client::METHOD_POST, '/storage/buckets/' . $bucketId . '/files', array_merge([ 'content-type' => 'multipart/form-data', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -897,6 +1028,8 @@ class RealtimeCustomClientTest extends Scope 'write' => ['role:all'], ]); + $fileId = $file['body']['$id']; + $response = json_decode($client->receive(), true); $this->assertArrayHasKey('type', $response); @@ -906,17 +1039,26 @@ class RealtimeCustomClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertCount(3, $response['data']['channels']); $this->assertContains('files', $response['data']['channels']); - $this->assertContains('buckets.' . $data['bucketId'] . '.files.' . $file['body']['$id'], $response['data']['channels']); - $this->assertContains('buckets.' . $data['bucketId'] . '.files', $response['data']['channels']); - $this->assertEquals("buckets.{$data['bucketId']}.files.{$file['body']['$id']}.create", $response['data']['event']); + $this->assertContains("buckets.{$bucketId}.files.{$fileId}", $response['data']['channels']); + $this->assertContains("buckets.{$bucketId}.files", $response['data']['channels']); + $this->assertContains("buckets.{$bucketId}.files.{$fileId}.create", $response['data']['events']); + $this->assertContains("buckets.{$bucketId}.files.{$fileId}", $response['data']['events']); + $this->assertContains("buckets.{$bucketId}.files.*.create", $response['data']['events']); + $this->assertContains("buckets.{$bucketId}.files.*", $response['data']['events']); + $this->assertContains("buckets.{$bucketId}", $response['data']['events']); + $this->assertContains("buckets.*.files.{$fileId}.create", $response['data']['events']); + $this->assertContains("buckets.*.files.{$fileId}", $response['data']['events']); + $this->assertContains("buckets.*.files.*.create", $response['data']['events']); + $this->assertContains("buckets.*.files.*", $response['data']['events']); + $this->assertContains("buckets.*", $response['data']['events']); $this->assertNotEmpty($response['data']['payload']); - $data['fileId'] = $file['body']['$id']; + $fileId = $file['body']['$id']; /** * Test File Update */ - $this->client->call(Client::METHOD_PUT, '/storage/buckets/' . $data['bucketId'] . '/files/' . $data['fileId'], array_merge([ + $this->client->call(Client::METHOD_PUT, '/storage/buckets/' . $bucketId . '/files/' . $fileId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -933,15 +1075,24 @@ class RealtimeCustomClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertCount(3, $response['data']['channels']); $this->assertContains('files', $response['data']['channels']); - $this->assertContains('buckets.' . $data['bucketId'] . '.files.' . $file['body']['$id'], $response['data']['channels']); - $this->assertContains('buckets.' . $data['bucketId'] . '.files', $response['data']['channels']); - $this->assertEquals("buckets.{$data['bucketId']}.files.{$file['body']['$id']}.update", $response['data']['event']); + $this->assertContains("buckets.{$bucketId}.files.{$fileId}", $response['data']['channels']); + $this->assertContains("buckets.{$bucketId}.files", $response['data']['channels']); + $this->assertContains("buckets.{$bucketId}.files.{$fileId}.update", $response['data']['events']); + $this->assertContains("buckets.{$bucketId}.files.{$fileId}", $response['data']['events']); + $this->assertContains("buckets.{$bucketId}.files.*.update", $response['data']['events']); + $this->assertContains("buckets.{$bucketId}.files.*", $response['data']['events']); + $this->assertContains("buckets.{$bucketId}", $response['data']['events']); + $this->assertContains("buckets.*.files.{$fileId}.update", $response['data']['events']); + $this->assertContains("buckets.*.files.{$fileId}", $response['data']['events']); + $this->assertContains("buckets.*.files.*.update", $response['data']['events']); + $this->assertContains("buckets.*.files.*", $response['data']['events']); + $this->assertContains("buckets.*", $response['data']['events']); $this->assertNotEmpty($response['data']['payload']); /** * Test File Delete */ - $this->client->call(Client::METHOD_DELETE, '/storage/buckets/' . $data['bucketId'] . '/files/' . $data['fileId'], array_merge([ + $this->client->call(Client::METHOD_DELETE, '/storage/buckets/' . $bucketId . '/files/' . $fileId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -955,9 +1106,18 @@ class RealtimeCustomClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertCount(3, $response['data']['channels']); $this->assertContains('files', $response['data']['channels']); - $this->assertContains('buckets.' . $data['bucketId'] . '.files.' . $file['body']['$id'], $response['data']['channels']); - $this->assertContains('buckets.' . $data['bucketId'] . '.files', $response['data']['channels']); - $this->assertEquals("buckets.{$data['bucketId']}.files.{$file['body']['$id']}.delete", $response['data']['event']); + $this->assertContains("buckets.{$bucketId}.files.{$fileId}", $response['data']['channels']); + $this->assertContains("buckets.{$bucketId}.files", $response['data']['channels']); + $this->assertContains("buckets.{$bucketId}.files.{$fileId}.delete", $response['data']['events']); + $this->assertContains("buckets.{$bucketId}.files.{$fileId}", $response['data']['events']); + $this->assertContains("buckets.{$bucketId}.files.*.delete", $response['data']['events']); + $this->assertContains("buckets.{$bucketId}.files.*", $response['data']['events']); + $this->assertContains("buckets.{$bucketId}", $response['data']['events']); + $this->assertContains("buckets.*.files.{$fileId}.delete", $response['data']['events']); + $this->assertContains("buckets.*.files.{$fileId}", $response['data']['events']); + $this->assertContains("buckets.*.files.*.delete", $response['data']['events']); + $this->assertContains("buckets.*.files.*", $response['data']['events']); + $this->assertContains("buckets.*", $response['data']['events']); $this->assertNotEmpty($response['data']['payload']); $client->close(); @@ -971,7 +1131,7 @@ class RealtimeCustomClientTest extends Scope $client = $this->getWebsocket(['executions'], [ 'origin' => 'http://localhost', - 'cookie' => 'a_session_'.$projectId.'=' . $session + 'cookie' => 'a_session_' . $projectId . '=' . $session ]); $response = json_decode($client->receive(), true); @@ -1007,11 +1167,12 @@ class RealtimeCustomClientTest extends Scope $folder = 'timeout'; $stderr = ''; - $stdout= ''; - $code = realpath(__DIR__ . '/../../../resources/functions'). "/$folder/code.tar.gz"; - Console::execute('cd '.realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $stdout, $stderr); + $stdout = ''; + $code = realpath(__DIR__ . '/../../../resources/functions') . "/{$folder}/code.tar.gz"; - $deployment = $this->client->call(Client::METHOD_POST, '/functions/'.$functionId.'/deployments', array_merge([ + Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/{$folder} && tar --exclude code.tar.gz -czf code.tar.gz .", '', $stdout, $stderr); + + $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([ 'content-type' => 'multipart/form-data', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -1028,7 +1189,7 @@ class RealtimeCustomClientTest extends Scope // Wait for deployment to be built. sleep(5); - $response = $this->client->call(Client::METHOD_PATCH, '/functions/'.$functionId.'/deployments/'.$deploymentId, array_merge([ + $response = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -1037,7 +1198,7 @@ class RealtimeCustomClientTest extends Scope $this->assertEquals($response['headers']['status-code'], 200); $this->assertNotEmpty($response['body']['$id']); - $execution = $this->client->call(Client::METHOD_POST, '/functions/'.$functionId.'/executions', array_merge([ + $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'] ], $this->getHeaders()), []); @@ -1048,6 +1209,8 @@ class RealtimeCustomClientTest extends Scope $response = json_decode($client->receive(), true); $responseUpdate = json_decode($client->receive(), true); + $executionId = $execution['body']['$id']; + $this->assertArrayHasKey('type', $response); $this->assertArrayHasKey('data', $response); $this->assertEquals('event', $response['type']); @@ -1056,9 +1219,18 @@ class RealtimeCustomClientTest extends Scope $this->assertCount(4, $response['data']['channels']); $this->assertContains('console', $response['data']['channels']); $this->assertContains('executions', $response['data']['channels']); - $this->assertContains('executions.' . $execution['body']['$id'], $response['data']['channels']); - $this->assertContains('functions.' . $execution['body']['functionId'], $response['data']['channels']); - $this->assertEquals("functions.{$functionId}.executions.{$execution['body']['$id']}.create", $response['data']['event']); + $this->assertContains("executions.{$executionId}", $response['data']['channels']); + $this->assertContains("functions.{$functionId}", $response['data']['channels']); + $this->assertContains("functions.{$functionId}.executions.{$executionId}.create", $response['data']['events']); + $this->assertContains("functions.{$functionId}.executions.{$executionId}", $response['data']['events']); + $this->assertContains("functions.{$functionId}.executions.*.create", $response['data']['events']); + $this->assertContains("functions.{$functionId}.executions.*", $response['data']['events']); + $this->assertContains("functions.{$functionId}", $response['data']['events']); + $this->assertContains("functions.*.executions.{$executionId}.create", $response['data']['events']); + $this->assertContains("functions.*.executions.{$executionId}", $response['data']['events']); + $this->assertContains("functions.*.executions.*.create", $response['data']['events']); + $this->assertContains("functions.*.executions.*", $response['data']['events']); + $this->assertContains("functions.*", $response['data']['events']); $this->assertNotEmpty($response['data']['payload']); $this->assertArrayHasKey('type', $responseUpdate); @@ -1069,15 +1241,24 @@ class RealtimeCustomClientTest extends Scope $this->assertCount(4, $responseUpdate['data']['channels']); $this->assertContains('console', $responseUpdate['data']['channels']); $this->assertContains('executions', $responseUpdate['data']['channels']); - $this->assertContains('executions.' . $execution['body']['$id'], $responseUpdate['data']['channels']); - $this->assertContains('functions.' . $execution['body']['functionId'], $responseUpdate['data']['channels']); - $this->assertEquals("functions.{$functionId}.executions.{$execution['body']['$id']}.update", $responseUpdate['data']['event']); + $this->assertContains("executions.{$executionId}", $responseUpdate['data']['channels']); + $this->assertContains("functions.{$functionId}", $responseUpdate['data']['channels']); + $this->assertContains("functions.{$functionId}.executions.{$executionId}.update", $responseUpdate['data']['events']); + $this->assertContains("functions.{$functionId}.executions.{$executionId}", $responseUpdate['data']['events']); + $this->assertContains("functions.{$functionId}.executions.*.update", $responseUpdate['data']['events']); + $this->assertContains("functions.{$functionId}.executions.*", $responseUpdate['data']['events']); + $this->assertContains("functions.{$functionId}", $responseUpdate['data']['events']); + $this->assertContains("functions.*.executions.{$executionId}.update", $responseUpdate['data']['events']); + $this->assertContains("functions.*.executions.{$executionId}", $responseUpdate['data']['events']); + $this->assertContains("functions.*.executions.*.update", $responseUpdate['data']['events']); + $this->assertContains("functions.*.executions.*", $responseUpdate['data']['events']); + $this->assertContains("functions.*", $responseUpdate['data']['events']); $this->assertNotEmpty($responseUpdate['data']['payload']); $client->close(); // Cleanup : Delete function - $response = $this->client->call(Client::METHOD_DELETE, '/functions/'. $functionId, [ + $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], @@ -1094,7 +1275,7 @@ class RealtimeCustomClientTest extends Scope $client = $this->getWebsocket(['teams'], [ 'origin' => 'http://localhost', - 'cookie' => 'a_session_'.$projectId.'=' . $session + 'cookie' => 'a_session_' . $projectId . '=' . $session ]); $response = json_decode($client->receive(), true); @@ -1133,14 +1314,17 @@ class RealtimeCustomClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertCount(2, $response['data']['channels']); $this->assertContains('teams', $response['data']['channels']); - $this->assertContains('teams.' . $teamId, $response['data']['channels']); - $this->assertEquals("teams.{$teamId}.create", $response['data']['event']); + $this->assertContains("teams.{$teamId}", $response['data']['channels']); + $this->assertContains("teams.{$teamId}.create", $response['data']['events']); + $this->assertContains("teams.{$teamId}", $response['data']['events']); + $this->assertContains("teams.*.create", $response['data']['events']); + $this->assertContains("teams.*", $response['data']['events']); $this->assertNotEmpty($response['data']['payload']); /** * Test Team Update */ - $team = $this->client->call(Client::METHOD_PUT, '/teams/'.$teamId, array_merge([ + $team = $this->client->call(Client::METHOD_PUT, '/teams/' . $teamId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $projectId, ], $this->getHeaders()), [ @@ -1159,8 +1343,11 @@ class RealtimeCustomClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertCount(2, $response['data']['channels']); $this->assertContains('teams', $response['data']['channels']); - $this->assertContains('teams.' . $teamId, $response['data']['channels']); - $this->assertEquals("teams.{$teamId}.update", $response['data']['event']); + $this->assertContains("teams.{$teamId}", $response['data']['channels']); + $this->assertContains("teams.{$teamId}.update", $response['data']['events']); + $this->assertContains("teams.{$teamId}", $response['data']['events']); + $this->assertContains("teams.*.update", $response['data']['events']); + $this->assertContains("teams.*", $response['data']['events']); $this->assertNotEmpty($response['data']['payload']); $client->close(); @@ -1181,7 +1368,7 @@ class RealtimeCustomClientTest extends Scope $client = $this->getWebsocket(['memberships'], [ 'origin' => 'http://localhost', - 'cookie' => 'a_session_'.$projectId.'='.$session + 'cookie' => 'a_session_' . $projectId . '=' . $session ]); $response = json_decode($client->receive(), true); @@ -1195,7 +1382,7 @@ class RealtimeCustomClientTest extends Scope $this->assertNotEmpty($response['data']['user']); $this->assertEquals($user['$id'], $response['data']['user']['$id']); - $response = $this->client->call(Client::METHOD_GET, '/teams/'.$teamId.'/memberships', array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamId . '/memberships', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -1206,7 +1393,7 @@ class RealtimeCustomClientTest extends Scope * Test Update Membership */ $roles = ['admin', 'editor', 'uncle']; - $this->client->call(Client::METHOD_PATCH, '/teams/'.$teamId.'/memberships/'.$membershipId, array_merge([ + $this->client->call(Client::METHOD_PATCH, '/teams/' . $teamId . '/memberships/' . $membershipId, array_merge([ 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -1223,10 +1410,19 @@ class RealtimeCustomClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertCount(2, $response['data']['channels']); $this->assertContains('memberships', $response['data']['channels']); - $this->assertContains('memberships.' . $membershipId, $response['data']['channels']); - $this->assertEquals("teams.{$teamId}.memberships.{$membershipId}.update", $response['data']['event']); + $this->assertContains("memberships.{$membershipId}", $response['data']['channels']); + $this->assertContains("teams.{$teamId}.memberships.{$membershipId}.update", $response['data']['events']); + $this->assertContains("teams.{$teamId}.memberships.{$membershipId}", $response['data']['events']); + $this->assertContains("teams.{$teamId}.memberships.*.update", $response['data']['events']); + $this->assertContains("teams.{$teamId}.memberships.*", $response['data']['events']); + $this->assertContains("teams.{$teamId}", $response['data']['events']); + $this->assertContains("teams.*.memberships.{$membershipId}.update", $response['data']['events']); + $this->assertContains("teams.*.memberships.{$membershipId}", $response['data']['events']); + $this->assertContains("teams.*.memberships.*.update", $response['data']['events']); + $this->assertContains("teams.*.memberships.*", $response['data']['events']); + $this->assertContains("teams.*", $response['data']['events']); $this->assertNotEmpty($response['data']['payload']); $client->close(); } -} \ No newline at end of file +} diff --git a/tests/e2e/Services/Webhooks/WebhooksBase.php b/tests/e2e/Services/Webhooks/WebhooksBase.php index 9ddc1119ac..815f27d45f 100644 --- a/tests/e2e/Services/Webhooks/WebhooksBase.php +++ b/tests/e2e/Services/Webhooks/WebhooksBase.php @@ -24,6 +24,8 @@ trait WebhooksBase 'permission' => 'document', ]); + $actorsId = $actors['body']['$id']; + $this->assertEquals($actors['headers']['status-code'], 201); $this->assertNotEmpty($actors['body']['$id']); @@ -32,7 +34,10 @@ trait WebhooksBase $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'database.collections.create'); + $this->assertStringContainsString('collections.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('collections.*.create', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$actorsId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$actorsId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -44,7 +49,7 @@ trait WebhooksBase $this->assertCount(1, $webhook['data']['$read']); $this->assertCount(1, $webhook['data']['$write']); - return array_merge(['actorsId' => $actors['body']['$id']]); + return array_merge(['actorsId' => $actorsId]); } /** @@ -52,7 +57,9 @@ trait WebhooksBase */ public function testCreateAttributes(array $data): array { - $firstName = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/attributes/string', array_merge([ + $actorsId = $data['actorsId']; + + $firstName = $this->client->call(Client::METHOD_POST, '/database/collections/' . $actorsId . '/attributes/string', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -62,7 +69,7 @@ trait WebhooksBase 'required' => true, ]); - $lastName = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/attributes/string', array_merge([ + $lastName = $this->client->call(Client::METHOD_POST, '/database/collections/' . $actorsId . '/attributes/string', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -72,7 +79,7 @@ trait WebhooksBase 'required' => true, ]); - $extra = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/attributes/string', array_merge([ + $extra = $this->client->call(Client::METHOD_POST, '/database/collections/' . $actorsId . '/attributes/string', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -82,6 +89,8 @@ trait WebhooksBase 'required' => false, ]); + $attributeId = $extra['body']['key']; + $this->assertEquals($firstName['headers']['status-code'], 201); $this->assertEquals($firstName['body']['key'], 'firstName'); $this->assertEquals($lastName['headers']['status-code'], 201); @@ -97,13 +106,22 @@ trait WebhooksBase $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'database.attributes.create'); + $this->assertStringContainsString('collections.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('collections.*.attributes.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('collections.*.attributes.*.create', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.*.attributes.{$actorsId}_{$attributeId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.*.attributes.{$actorsId}_{$attributeId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$actorsId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$actorsId}.attributes.*", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$actorsId}.attributes.*.create", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$actorsId}.attributes.{$actorsId}_{$attributeId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$actorsId}.attributes.{$actorsId}_{$attributeId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); $this->assertNotEmpty($webhook['data']['key']); $this->assertEquals($webhook['data']['key'], 'extra'); - + $removed = $this->client->call(Client::METHOD_DELETE, '/database/collections/' . $data['actorsId'] . '/attributes/' . $extra['body']['key'], array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -117,7 +135,16 @@ trait WebhooksBase // $this->assertEquals($webhook['method'], 'DELETE'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'database.attributes.delete'); + $this->assertStringContainsString('collections.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('collections.*.attributes.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('collections.*.attributes.*.delete', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.*.attributes.{$actorsId}_{$attributeId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.*.attributes.{$actorsId}_{$attributeId}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$actorsId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$actorsId}.attributes.*", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$actorsId}.attributes.*.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$actorsId}.attributes.{$actorsId}_{$attributeId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$actorsId}.attributes.{$actorsId}_{$attributeId}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -132,7 +159,12 @@ trait WebhooksBase */ public function testCreateDocument(array $data): array { - $document = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/documents', array_merge([ + $actorsId = $data['actorsId']; + + /** + * Test for SUCCESS + */ + $document = $this->client->call(Client::METHOD_POST, '/database/collections/' . $actorsId . '/documents', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -140,12 +172,13 @@ trait WebhooksBase 'data' => [ 'firstName' => 'Chris', 'lastName' => 'Evans', - ], 'read' => ['role:all'], 'write' => ['role:all'], ]); + $documentId = $document['body']['$id']; + $this->assertEquals($document['headers']['status-code'], 201); $this->assertNotEmpty($document['body']['$id']); @@ -154,7 +187,16 @@ trait WebhooksBase $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'database.documents.create'); + $this->assertStringContainsString('collections.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('collections.*.documents.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('collections.*.documents.*.create', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.*.documents.{$documentId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.*.documents.{$documentId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$actorsId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$actorsId}.documents.*", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$actorsId}.documents.*.create", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$actorsId}.documents.{$documentId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$actorsId}.documents.{$documentId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -177,7 +219,12 @@ trait WebhooksBase */ public function testUpdateDocument(array $data): array { - $document = $this->client->call(Client::METHOD_PATCH, '/database/collections/' . $data['actorsId'] . '/documents/'.$data['documentId'], array_merge([ + $actorsId = $data['actorsId']; + + /** + * Test for SUCCESS + */ + $document = $this->client->call(Client::METHOD_PATCH, '/database/collections/' . $actorsId . '/documents/' . $data['documentId'], array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -189,6 +236,8 @@ trait WebhooksBase 'write' => ['role:all'], ]); + $documentId = $document['body']['$id']; + $this->assertEquals($document['headers']['status-code'], 200); $this->assertNotEmpty($document['body']['$id']); @@ -197,7 +246,16 @@ trait WebhooksBase $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'database.documents.update'); + $this->assertStringContainsString('collections.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('collections.*.documents.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('collections.*.documents.*.update', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.*.documents.{$documentId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.*.documents.{$documentId}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$actorsId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$actorsId}.documents.*", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$actorsId}.documents.*.update", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$actorsId}.documents.{$documentId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$actorsId}.documents.{$documentId}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -218,7 +276,12 @@ trait WebhooksBase */ public function testDeleteDocument(array $data): array { - $document = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/documents', array_merge([ + $actorsId = $data['actorsId']; + + /** + * Test for SUCCESS + */ + $document = $this->client->call(Client::METHOD_POST, '/database/collections/' . $actorsId . '/documents', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -226,16 +289,18 @@ trait WebhooksBase 'data' => [ 'firstName' => 'Bradly', 'lastName' => 'Cooper', - + ], 'read' => ['role:all'], 'write' => ['role:all'], ]); + $documentId = $document['body']['$id']; + $this->assertEquals($document['headers']['status-code'], 201); $this->assertNotEmpty($document['body']['$id']); - $document = $this->client->call(Client::METHOD_DELETE, '/database/collections/' . $data['actorsId'] . '/documents/' . $document['body']['$id'], array_merge([ + $document = $this->client->call(Client::METHOD_DELETE, '/database/collections/' . $actorsId . '/documents/' . $document['body']['$id'], array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -247,7 +312,16 @@ trait WebhooksBase $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'database.documents.delete'); + $this->assertStringContainsString('collections.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('collections.*.documents.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('collections.*.documents.*.delete', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.*.documents.{$documentId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.*.documents.{$documentId}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$actorsId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$actorsId}.documents.*", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$actorsId}.documents.*.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$actorsId}.documents.{$documentId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$actorsId}.documents.{$documentId}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -280,7 +354,9 @@ trait WebhooksBase 'read' => ['role:all'], 'write' => ['role:all'] ]); - + + $bucketId = $bucket['body']['$id']; + $this->assertEquals($bucket['headers']['status-code'], 201); $this->assertNotEmpty($bucket['body']['$id']); @@ -289,7 +365,10 @@ trait WebhooksBase $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'storage.buckets.create'); + $this->assertStringContainsString('buckets.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('buckets.*.create', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("buckets.{$bucketId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("buckets.{$bucketId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -299,8 +378,8 @@ trait WebhooksBase $this->assertEquals(true, $webhook['data']['enabled']); $this->assertIsArray($webhook['data']['$read']); $this->assertIsArray($webhook['data']['$write']); - - return array_merge(['bucketId' => $bucket['body']['$id']]); + + return array_merge(['bucketId' => $bucketId]); } /** @@ -308,6 +387,8 @@ trait WebhooksBase */ public function testUpdateStorageBucket(array $data): array { + $bucketId = $data['bucketId']; + /** * Test for SUCCESS */ @@ -320,7 +401,7 @@ trait WebhooksBase 'permission' => 'file', 'enabled' => false, ]); - + $this->assertEquals($bucket['headers']['status-code'], 200); $this->assertNotEmpty($bucket['body']['$id']); @@ -329,7 +410,10 @@ trait WebhooksBase $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'storage.buckets.update'); + $this->assertStringContainsString('buckets.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('buckets.*.update', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("buckets.{$bucketId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("buckets.{$bucketId}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -339,7 +423,7 @@ trait WebhooksBase $this->assertEquals(false, $webhook['data']['enabled']); $this->assertIsArray($webhook['data']['$read']); $this->assertIsArray($webhook['data']['$write']); - + return array_merge(['bucketId' => $bucket['body']['$id']]); } @@ -348,6 +432,8 @@ trait WebhooksBase */ public function testCreateBucketFile(array $data): array { + $bucketId = $data['bucketId']; + //enable bucket $bucket = $this->client->call(Client::METHOD_PUT, '/storage/buckets/' . $data['bucketId'], array_merge([ 'content-type' => 'application/json', @@ -358,12 +444,12 @@ trait WebhooksBase 'permission' => 'file', 'enabled' => true, ]); - + $this->assertEquals($bucket['headers']['status-code'], 200); /** * Test for SUCCESS */ - $file = $this->client->call(Client::METHOD_POST, '/storage/buckets/'. $data['bucketId'] . '/files', array_merge([ + $file = $this->client->call(Client::METHOD_POST, '/storage/buckets/' . $data['bucketId'] . '/files', array_merge([ 'content-type' => 'multipart/form-data', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -374,6 +460,8 @@ trait WebhooksBase 'folderId' => 'xyz', ]); + $fileId = $file['body']['$id']; + $this->assertEquals($file['headers']['status-code'], 201); $this->assertNotEmpty($file['body']['$id']); @@ -382,7 +470,16 @@ trait WebhooksBase $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'storage.files.create'); + $this->assertStringContainsString('buckets.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('buckets.*.files.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('buckets.*.files.*.create', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("buckets.*.files.{$fileId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("buckets.*.files.{$fileId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("buckets.{$bucketId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("buckets.{$bucketId}.files.*", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("buckets.{$bucketId}.files.*.create", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("buckets.{$bucketId}.files.{$fileId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("buckets.{$bucketId}.files.{$fileId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -396,22 +493,23 @@ trait WebhooksBase $this->assertEquals($webhook['data']['mimeType'], 'image/png'); $this->assertEquals($webhook['data']['sizeOriginal'], 47218); - /** - * Test for FAILURE - */ - $data ['fileId'] = $file['body']['$id']; + $data['fileId'] = $fileId; + return $data; } - + /** * @depends testCreateBucketFile */ public function testUpdateBucketFile(array $data): array { + $bucketId = $data['bucketId']; + $fileId = $data['fileId']; + /** * Test for SUCCESS */ - $file = $this->client->call(Client::METHOD_PUT, '/storage/buckets/' . $data['bucketId'] . '/files/' . $data['fileId'], array_merge([ + $file = $this->client->call(Client::METHOD_PUT, '/storage/buckets/' . $bucketId . '/files/' . $fileId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -427,7 +525,16 @@ trait WebhooksBase $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'storage.files.update'); + $this->assertStringContainsString('buckets.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('buckets.*.files.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('buckets.*.files.*.update', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("buckets.*.files.{$fileId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("buckets.*.files.{$fileId}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("buckets.{$bucketId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("buckets.{$bucketId}.files.*", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("buckets.{$bucketId}.files.*.update", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("buckets.{$bucketId}.files.{$fileId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("buckets.{$bucketId}.files.{$fileId}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -440,15 +547,18 @@ trait WebhooksBase $this->assertNotEmpty($webhook['data']['signature']); $this->assertEquals($webhook['data']['mimeType'], 'image/png'); $this->assertEquals($webhook['data']['sizeOriginal'], 47218); - + return $data; } - + /** * @depends testUpdateBucketFile */ public function testDeleteBucketFile(array $data): array { + $bucketId = $data['bucketId']; + $fileId = $data['fileId']; + /** * Test for SUCCESS */ @@ -465,7 +575,16 @@ trait WebhooksBase $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'storage.files.delete'); + $this->assertStringContainsString('buckets.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('buckets.*.files.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('buckets.*.files.*.delete', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("buckets.*.files.{$fileId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("buckets.*.files.{$fileId}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("buckets.{$bucketId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("buckets.{$bucketId}.files.*", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("buckets.{$bucketId}.files.*.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("buckets.{$bucketId}.files.{$fileId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("buckets.{$bucketId}.files.{$fileId}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -478,24 +597,25 @@ trait WebhooksBase $this->assertNotEmpty($webhook['data']['signature']); $this->assertEquals($webhook['data']['mimeType'], 'image/png'); $this->assertEquals($webhook['data']['sizeOriginal'], 47218); - + return $data; } - /** + /** * @depends testDeleteBucketFile */ public function testDeleteStorageBucket(array $data) { + $bucketId = $data['bucketId']; /** * Test for SUCCESS */ - $bucket = $this->client->call(Client::METHOD_DELETE, '/storage/buckets/' . $data['bucketId'] , array_merge([ + $bucket = $this->client->call(Client::METHOD_DELETE, '/storage/buckets/' . $bucketId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ])); - + $this->assertEquals($bucket['headers']['status-code'], 204); $this->assertEmpty($bucket['body']); @@ -504,7 +624,10 @@ trait WebhooksBase $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'storage.buckets.delete'); + $this->assertStringContainsString('buckets.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('buckets.*.delete', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("buckets.{$bucketId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("buckets.{$bucketId}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -529,6 +652,8 @@ trait WebhooksBase 'name' => 'Arsenal' ]); + $teamId = $team['body']['$id']; + $this->assertEquals(201, $team['headers']['status-code']); $this->assertNotEmpty($team['body']['$id']); @@ -537,7 +662,10 @@ trait WebhooksBase $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'teams.create'); + $this->assertStringContainsString('teams.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('teams.*.create', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("teams.{$teamId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("teams.{$teamId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -551,7 +679,7 @@ trait WebhooksBase /** * Test for FAILURE */ - return ['teamId' => $team['body']['$id']]; + return ['teamId' => $teamId]; } /** @@ -559,10 +687,11 @@ trait WebhooksBase */ public function testUpdateTeam($data): array { + $teamId = $data['teamId']; /** * Test for SUCCESS */ - $team = $this->client->call(Client::METHOD_PUT, '/teams/'.$data['teamId'], array_merge([ + $team = $this->client->call(Client::METHOD_PUT, '/teams/' . $teamId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -577,7 +706,10 @@ trait WebhooksBase $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'teams.update'); + $this->assertStringContainsString('teams.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('teams.*.update', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("teams.{$teamId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("teams.{$teamId}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -607,10 +739,12 @@ trait WebhooksBase 'name' => 'Chelsea' ]); + $teamId = $team['body']['$id']; + $this->assertEquals(201, $team['headers']['status-code']); $this->assertNotEmpty($team['body']['$id']); - $team = $this->client->call(Client::METHOD_DELETE, '/teams/'.$team['body']['$id'], array_merge([ + $team = $this->client->call(Client::METHOD_DELETE, '/teams/' . $team['body']['$id'], array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -620,7 +754,10 @@ trait WebhooksBase $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'teams.delete'); + $this->assertStringContainsString('teams.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('teams.*.delete', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("teams.{$teamId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("teams.{$teamId}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -642,13 +779,13 @@ trait WebhooksBase */ public function testCreateTeamMembership($data): array { - $teamUid = $data['teamId'] ?? ''; - $email = uniqid().'friend@localhost.test'; + $teamId = $data['teamId'] ?? ''; + $email = uniqid() . 'friend@localhost.test'; /** * Test for SUCCESS */ - $team = $this->client->call(Client::METHOD_POST, '/teams/'.$teamUid.'/memberships', array_merge([ + $team = $this->client->call(Client::METHOD_POST, '/teams/' . $teamId . '/memberships', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -664,15 +801,23 @@ trait WebhooksBase $lastEmail = $this->getLastEmail(); $secret = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 256); - $membershipUid = substr($lastEmail['text'], strpos($lastEmail['text'], '?membershipId=', 0) + 14, 20); - $userUid = substr($lastEmail['text'], strpos($lastEmail['text'], '&userId=', 0) + 8, 20); + $membershipId = $team['body']['$id']; $webhook = $this->getLastRequest(); $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'teams.memberships.create'); + $this->assertStringContainsString('teams.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('teams.*.memberships.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('teams.*.memberships.*.create', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("teams.*.memberships.{$membershipId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("teams.*.memberships.{$membershipId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("teams.{$teamId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("teams.{$teamId}.memberships.*", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("teams.{$teamId}.memberships.*.create", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("teams.{$teamId}.memberships.{$membershipId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("teams.{$teamId}.memberships.{$membershipId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -688,25 +833,25 @@ trait WebhooksBase * Test for FAILURE */ return [ - 'teamId' => $teamUid, + 'teamId' => $teamId, 'secret' => $secret, - 'membershipId' => $membershipUid, + 'membershipId' => $membershipId, 'userId' => $webhook['data']['userId'], ]; } /** - * @depends testCreateTeam + * @depends testCreateTeamMembership */ - public function testDeleteTeamMembership($data): array + public function testDeleteTeamMembership($data): void { - $teamUid = $data['teamId'] ?? ''; - $email = uniqid().'friend@localhost.test'; + $teamId = $data['teamId'] ?? ''; + $email = uniqid() . 'friend@localhost.test'; /** * Test for SUCCESS */ - $team = $this->client->call(Client::METHOD_POST, '/teams/'.$teamUid.'/memberships', array_merge([ + $team = $this->client->call(Client::METHOD_POST, '/teams/' . $teamId . '/memberships', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -716,10 +861,12 @@ trait WebhooksBase 'url' => 'http://localhost:5000/join-us#title' ]); + $membershipId = $team['body']['$id'] ?? ''; + $this->assertEquals(201, $team['headers']['status-code']); $this->assertNotEmpty($team['body']['$id']); - - $team = $this->client->call(Client::METHOD_DELETE, '/teams/'.$teamUid.'/memberships/'.$team['body']['$id'], array_merge([ + + $team = $this->client->call(Client::METHOD_DELETE, '/teams/' . $teamId . '/memberships/' . $team['body']['$id'], array_merge([ 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -732,7 +879,16 @@ trait WebhooksBase $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'teams.memberships.delete'); + $this->assertStringContainsString('teams.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('teams.*.memberships.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('teams.*.memberships.*.delete', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("teams.*.memberships.{$membershipId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("teams.*.memberships.{$membershipId}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("teams.{$teamId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("teams.{$teamId}.memberships.*", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("teams.{$teamId}.memberships.*.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("teams.{$teamId}.memberships.{$membershipId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("teams.{$teamId}.memberships.{$membershipId}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -743,10 +899,5 @@ trait WebhooksBase $this->assertCount(2, $webhook['data']['roles']); $this->assertIsInt($webhook['data']['joined']); $this->assertEquals(('server' === $this->getSide()), $webhook['data']['confirm']); - - /** - * Test for FAILURE - */ - return []; } -} \ No newline at end of file +} diff --git a/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php b/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php index 355c8f5855..5789f7ef8c 100644 --- a/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php +++ b/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php @@ -13,9 +13,9 @@ class WebhooksCustomClientTest extends Scope use ProjectCustom; use SideClient; - public function testCreateAccount():array + public function testCreateAccount(): array { - $email = uniqid().'user@localhost.test'; + $email = uniqid() . 'user@localhost.test'; $password = 'password'; $name = 'User Name'; @@ -43,7 +43,10 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.create'); + $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*.create', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event'],); + $this->assertStringContainsString("users.{$id}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -64,9 +67,9 @@ class WebhooksCustomClientTest extends Scope ]; } - public function testDeleteAccount():array + public function testDeleteAccount(): array { - $email = uniqid().'user1@localhost.test'; + $email = uniqid() . 'user1@localhost.test'; $password = 'password'; $name = 'User Name 1'; @@ -95,14 +98,14 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($accountSession['headers']['status-code'], 201); - $sessionId = $accountSession['body']['$id']; - $session = $this->client->parseCookie((string)$accountSession['headers']['set-cookie'])['a_session_'.$this->getProject()['$id']]; + $id = $account['body']['$id']; + $session = $this->client->parseCookie((string)$accountSession['headers']['set-cookie'])['a_session_' . $this->getProject()['$id']]; $account = $this->client->call(Client::METHOD_DELETE, '/account', array_merge([ 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, + 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, ])); $this->assertEquals($account['headers']['status-code'], 204); @@ -113,7 +116,10 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.delete'); + $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*.delete', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -132,8 +138,9 @@ class WebhooksCustomClientTest extends Scope /** * @depends testCreateAccount */ - public function testCreateAccountSession($data):array + public function testCreateAccountSession($data): array { + $id = $data['id'] ?? ''; $email = $data['email'] ?? ''; $password = $data['password'] ?? ''; @@ -152,14 +159,23 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($accountSession['headers']['status-code'], 201); $sessionId = $accountSession['body']['$id']; - $session = $this->client->parseCookie((string)$accountSession['headers']['set-cookie'])['a_session_'.$this->getProject()['$id']]; + $session = $this->client->parseCookie((string)$accountSession['headers']['set-cookie'])['a_session_' . $this->getProject()['$id']]; $webhook = $this->getLastRequest(); $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.sessions.create'); + $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*.sessions.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*.sessions.*.create', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.*.sessions.{$sessionId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.*.sessions.{$sessionId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.sessions.*", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.sessions.*.create", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.sessions.{$sessionId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.sessions.{$sessionId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -198,8 +214,9 @@ class WebhooksCustomClientTest extends Scope /** * @depends testCreateAccount */ - public function testDeleteAccountSession($data):array + public function testDeleteAccountSession($data): array { + $id = $data['id'] ?? ''; $email = $data['email'] ?? ''; $password = $data['password'] ?? ''; @@ -216,15 +233,15 @@ class WebhooksCustomClientTest extends Scope ]); $sessionId = $accountSession['body']['$id']; - $session = $this->client->parseCookie((string)$accountSession['headers']['set-cookie'])['a_session_'.$this->getProject()['$id']]; + $session = $this->client->parseCookie((string)$accountSession['headers']['set-cookie'])['a_session_' . $this->getProject()['$id']]; $this->assertEquals($accountSession['headers']['status-code'], 201); - $accountSession = $this->client->call(Client::METHOD_DELETE, '/account/sessions/'.$sessionId, array_merge([ + $accountSession = $this->client->call(Client::METHOD_DELETE, '/account/sessions/' . $sessionId, array_merge([ 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, + 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, ])); $this->assertEquals($accountSession['headers']['status-code'], 204); @@ -234,7 +251,16 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.sessions.delete'); + $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*.sessions.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*.sessions.*.delete', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.*.sessions.{$sessionId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.*.sessions.{$sessionId}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.sessions.*", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.sessions.*.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.sessions.{$sessionId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.sessions.{$sessionId}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -270,8 +296,9 @@ class WebhooksCustomClientTest extends Scope /** * @depends testCreateAccount */ - public function testDeleteAccountSessions($data):array + public function testDeleteAccountSessions($data): array { + $id = $data['id'] ?? ''; $email = $data['email'] ?? ''; $password = $data['password'] ?? ''; @@ -287,7 +314,8 @@ class WebhooksCustomClientTest extends Scope 'password' => $password, ]); - $session = $this->client->parseCookie((string)$accountSession['headers']['set-cookie'])['a_session_'.$this->getProject()['$id']]; + $sessionId = $accountSession['body']['$id']; + $session = $this->client->parseCookie((string)$accountSession['headers']['set-cookie'])['a_session_' . $this->getProject()['$id']]; $this->assertEquals($accountSession['headers']['status-code'], 201); @@ -295,7 +323,7 @@ class WebhooksCustomClientTest extends Scope 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, + 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, ])); $this->assertEquals($accountSession['headers']['status-code'], 204); @@ -305,7 +333,16 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.sessions.delete'); + $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*.sessions.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*.sessions.*.delete', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.*.sessions.{$sessionId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.*.sessions.{$sessionId}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.sessions.*", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.sessions.*.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.sessions.{$sessionId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.sessions.{$sessionId}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -348,7 +385,7 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($accountSession['headers']['status-code'], 201); $sessionId = $accountSession['body']['$id']; - $session = $this->client->parseCookie((string)$accountSession['headers']['set-cookie'])['a_session_'.$this->getProject()['$id']]; + $session = $this->client->parseCookie((string)$accountSession['headers']['set-cookie'])['a_session_' . $this->getProject()['$id']]; return array_merge($data, [ 'sessionId' => $sessionId, @@ -370,7 +407,7 @@ class WebhooksCustomClientTest extends Scope 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, + 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, ]), [ 'name' => $newName ]); @@ -383,7 +420,12 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.update.name'); + $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*.update', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*.update.name', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.update.name", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -413,7 +455,7 @@ class WebhooksCustomClientTest extends Scope 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, + 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, ]), [ 'password' => 'new-password', 'oldPassword' => $password, @@ -427,7 +469,12 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.update.password'); + $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*.update', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*.update.password', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.update.password", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -452,14 +499,14 @@ class WebhooksCustomClientTest extends Scope { $id = $data['id'] ?? ''; $email = $data['email'] ?? ''; - $newEmail = uniqid().'new@localhost.test'; + $newEmail = uniqid() . 'new@localhost.test'; $session = $data['session'] ?? ''; $account = $this->client->call(Client::METHOD_PATCH, '/account/email', array_merge([ 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, + 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, ]), [ 'email' => $newEmail, 'password' => 'new-password', @@ -473,7 +520,12 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.update.email'); + $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*.update', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*.update.email', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.update.email", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -504,7 +556,7 @@ class WebhooksCustomClientTest extends Scope 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, + 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, ]), [ 'prefs' => [ 'prefKey1' => 'prefValue1', @@ -520,7 +572,12 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.update.prefs'); + $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*.update', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*.update.prefs', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.update.prefs", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -546,7 +603,6 @@ class WebhooksCustomClientTest extends Scope { $id = $data['id'] ?? ''; $email = $data['email'] ?? ''; - $session = $data['session'] ?? ''; $recovery = $this->client->call(Client::METHOD_POST, '/account/recovery', array_merge([ 'origin' => 'http://localhost', @@ -557,6 +613,8 @@ class WebhooksCustomClientTest extends Scope 'url' => 'http://localhost/recovery', ]); + $recoveryId = $recovery['body']['$id']; + $this->assertEquals(201, $recovery['headers']['status-code']); $this->assertIsArray($recovery['body']); @@ -565,18 +623,27 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.recovery.create'); + $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*.recovery.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*.recovery.*.create', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.*.recovery.{$recoveryId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.*.recovery.{$recoveryId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.recovery.*", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.recovery.*.create", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.recovery.{$recoveryId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.recovery.{$recoveryId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); - $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id']), true); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-User-Id'], $id); $this->assertNotEmpty($webhook['data']['$id']); $this->assertNotEmpty($webhook['data']['userId']); $this->assertNotEmpty($webhook['data']['secret']); $this->assertIsNumeric($webhook['data']['expire']); $data['secret'] = $webhook['data']['secret']; - + return $data; } @@ -602,6 +669,8 @@ class WebhooksCustomClientTest extends Scope 'passwordAgain' => $password, ]); + $recoveryId = $recovery['body']['$id']; + $this->assertEquals(200, $recovery['headers']['status-code']); $this->assertIsArray($recovery['body']); @@ -610,7 +679,16 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.recovery.update'); + $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*.recovery.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*.recovery.*.update', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.*.recovery.{$recoveryId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.*.recovery.{$recoveryId}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.recovery.*", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.recovery.*.update", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.recovery.{$recoveryId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.recovery.{$recoveryId}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -638,11 +716,13 @@ class WebhooksCustomClientTest extends Scope 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, + 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, ]), [ 'url' => 'http://localhost/verification', ]); + $verificationId = $verification['body']['$id']; + $this->assertEquals(201, $verification['headers']['status-code']); $this->assertIsArray($verification['body']); @@ -651,7 +731,16 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.verification.create'); + $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*.verification.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*.verification.*.create', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.*.verification.{$verificationId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.*.verification.{$verificationId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.verification.*", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.verification.*.create", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.verification.{$verificationId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.verification.{$verificationId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -680,12 +769,14 @@ class WebhooksCustomClientTest extends Scope 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, + 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, ]), [ 'userId' => $id, 'secret' => $secret, ]); + $verificationId = $verification['body']['$id']; + $this->assertEquals(200, $verification['headers']['status-code']); $this->assertIsArray($verification['body']); @@ -694,7 +785,16 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'account.verification.update'); + $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*.verification.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*.verification.*.update', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.*.verification.{$verificationId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.*.verification.{$verificationId}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.verification.*", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.verification.*.update", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.verification.{$verificationId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.verification.{$verificationId}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -722,7 +822,7 @@ class WebhooksCustomClientTest extends Scope /** * Test for SUCCESS */ - $team = $this->client->call(Client::METHOD_PATCH, '/teams/'.$teamUid.'/memberships/'.$membershipUid.'/status', array_merge([ + $team = $this->client->call(Client::METHOD_PATCH, '/teams/' . $teamUid . '/memberships/' . $membershipUid . '/status', array_merge([ 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -739,7 +839,20 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'teams.memberships.update.status'); + $this->assertStringContainsString('teams.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('teams.*.memberships.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('teams.*.memberships.*.update', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('teams.*.memberships.*.update.status', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("teams.*.memberships.{$membershipUid}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("teams.*.memberships.{$membershipUid}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("teams.*.memberships.{$membershipUid}.update.status", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("teams.{$teamUid}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("teams.{$teamUid}.memberships.*", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("teams.{$teamUid}.memberships.*.update", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("teams.{$teamUid}.memberships.*.update.status", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("teams.{$teamUid}.memberships.{$membershipUid}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("teams.{$teamUid}.memberships.{$membershipUid}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("teams.{$teamUid}.memberships.{$membershipUid}.update.status", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -756,4 +869,4 @@ class WebhooksCustomClientTest extends Scope */ return []; } -} \ No newline at end of file +} diff --git a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php index b79ae37d72..bb8c35a1ec 100644 --- a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php +++ b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php @@ -20,10 +20,12 @@ class WebhooksCustomServerTest extends Scope */ public function testUpdateCollection($data): array { + $id = $data['actorsId']; + /** * Test for SUCCESS */ - $actors = $this->client->call(Client::METHOD_PUT, '/database/collections/'.$data['actorsId'], array_merge([ + $actors = $this->client->call(Client::METHOD_PUT, '/database/collections/' . $id, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -31,7 +33,7 @@ class WebhooksCustomServerTest extends Scope 'name' => 'Actors1', 'permission' => 'document', ]); - + $this->assertEquals($actors['headers']['status-code'], 200); $this->assertNotEmpty($actors['body']['$id']); @@ -40,7 +42,10 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'database.collections.update'); + $this->assertStringContainsString('collections.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('collections.*.update', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$id}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -60,6 +65,8 @@ class WebhooksCustomServerTest extends Scope */ public function testCreateDeleteIndexes($data): array { + $actorsId = $data['actorsId']; + $index = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/indexes', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -71,6 +78,7 @@ class WebhooksCustomServerTest extends Scope 'orders' => ['ASC', 'ASC'], ]); + $indexKey = $index['body']['key']; $this->assertEquals($index['headers']['status-code'], 201); $this->assertEquals($index['body']['key'], 'fullname'); @@ -82,7 +90,16 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'database.indexes.create'); + $this->assertStringContainsString('collections.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('collections.*.indexes.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('collections.*.indexes.*.create', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.*.indexes.{$actorsId}_{$indexKey}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.*.indexes.{$actorsId}_{$indexKey}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$actorsId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$actorsId}.indexes.*", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$actorsId}.indexes.*.create", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$actorsId}.indexes.{$actorsId}_{$indexKey}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$actorsId}.indexes.{$actorsId}_{$indexKey}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -101,7 +118,16 @@ class WebhooksCustomServerTest extends Scope // $this->assertEquals($webhook['method'], 'DELETE'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'database.indexes.delete'); + $this->assertStringContainsString('collections.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('collections.*.indexes.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('collections.*.indexes.*.delete', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.*.indexes.{$actorsId}_{$indexKey}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.*.indexes.{$actorsId}_{$indexKey}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$actorsId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$actorsId}.indexes.*", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$actorsId}.indexes.*.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$actorsId}.indexes.{$actorsId}_{$indexKey}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$actorsId}.indexes.{$actorsId}_{$indexKey}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -127,10 +153,12 @@ class WebhooksCustomServerTest extends Scope 'permission' => 'document' ]); + $id = $actors['body']['$id']; + $this->assertEquals($actors['headers']['status-code'], 201); $this->assertNotEmpty($actors['body']['$id']); - $actors = $this->client->call(Client::METHOD_DELETE, '/database/collections/'.$actors['body']['$id'], array_merge([ + $actors = $this->client->call(Client::METHOD_DELETE, '/database/collections/' . $actors['body']['$id'], array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -143,7 +171,10 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'database.collections.delete'); + $this->assertStringContainsString('collections.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('collections.*.delete', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("collections.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event'],); + $this->assertStringContainsString("collections.{$id}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -158,9 +189,9 @@ class WebhooksCustomServerTest extends Scope return []; } - public function testCreateUser():array + public function testCreateUser(): array { - $email = uniqid().'user@localhost.test'; + $email = uniqid() . 'user@localhost.test'; $password = 'password'; $name = 'User Name'; @@ -187,7 +218,10 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'users.create'); + $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*.create', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event'],); + $this->assertStringContainsString("users.{$id}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -206,15 +240,17 @@ class WebhooksCustomServerTest extends Scope return ['userId' => $user['body']['$id'], 'name' => $user['body']['name'], 'email' => $user['body']['email']]; } - /** + /** * @depends testCreateUser */ - public function testUpdateUserPrefs(array $data):array + public function testUpdateUserPrefs(array $data): array { + $id = $data['userId']; + /** * Test for SUCCESS */ - $user = $this->client->call(Client::METHOD_PATCH, '/users/' . $data['userId'] . '/prefs', array_merge([ + $user = $this->client->call(Client::METHOD_PATCH, '/users/' . $id . '/prefs', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -229,7 +265,12 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'users.update.prefs'); + $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*.update', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*.update.prefs', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.update.prefs", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -242,8 +283,10 @@ class WebhooksCustomServerTest extends Scope /** * @depends testUpdateUserPrefs */ - public function testUpdateUserStatus(array $data):array + public function testUpdateUserStatus(array $data): array { + $id = $data['userId']; + /** * Test for SUCCESS */ @@ -262,7 +305,12 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'users.update.status'); + $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*.update', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*.update.status', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.update.status", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -277,12 +325,14 @@ class WebhooksCustomServerTest extends Scope return $data; } - + /** * @depends testUpdateUserStatus */ - public function testDeleteUser(array $data):array + public function testDeleteUser(array $data): array { + $id = $data['userId']; + /** * Test for SUCCESS */ @@ -298,7 +348,10 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'users.delete'); + $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*.delete', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("users.{$id}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -314,7 +367,7 @@ class WebhooksCustomServerTest extends Scope return $data; } - public function testCreateFunction():array + public function testCreateFunction(): array { /** * Test for SUCCESS @@ -330,7 +383,7 @@ class WebhooksCustomServerTest extends Scope 'timeout' => 10, ]); - $functionId = $function['body']['$id'] ?? ''; + $id = $function['body']['$id'] ?? ''; $this->assertEquals($function['headers']['status-code'], 201); $this->assertNotEmpty($function['body']['$id']); @@ -340,7 +393,10 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'functions.create'); + $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('functions.*.create', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.{$id}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -350,19 +406,21 @@ class WebhooksCustomServerTest extends Scope */ return [ - 'functionId' => $functionId, + 'functionId' => $id, ]; } /** * @depends testCreateFunction */ - public function testUpdateFunction($data):array + public function testUpdateFunction($data): array { - /** - * Test for SUCCESS - */ - $function = $this->client->call(Client::METHOD_PUT, '/functions/'.$data['functionId'], array_merge([ + $id = $data['functionId']; + + /** + * Test for SUCCESS + */ + $function = $this->client->call(Client::METHOD_PUT, '/functions/' . $data['functionId'], array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -383,29 +441,32 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'functions.update'); + $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('functions.*.update', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.{$id}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); return $data; } - + /** * @depends testUpdateFunction */ - public function testCreateDeployment($data):array + public function testCreateDeployment($data): array { /** * Test for SUCCESS */ $stderr = ''; - $stdout= ''; + $stdout = ''; $folder = 'timeout'; - $code = realpath(__DIR__ . '/../../../resources/functions'). "/$folder/code.tar.gz"; - Console::execute('cd '.realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $stdout, $stderr); + $code = realpath(__DIR__ . '/../../../resources/functions') . "/{$folder}/code.tar.gz"; + Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/{$folder} && tar --exclude code.tar.gz -czf code.tar.gz .", '', $stdout, $stderr); - $deployment = $this->client->call(Client::METHOD_POST, '/functions/'.$data['functionId'].'/deployments', array_merge([ + $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/deployments', array_merge([ 'content-type' => 'multipart/form-data', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -413,6 +474,7 @@ class WebhooksCustomServerTest extends Scope 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)) ]); + $id = $data['functionId'] ?? ''; $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals($deployment['headers']['status-code'], 201); @@ -423,7 +485,12 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'functions.deployments.create'); + $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('functions.*.deployments.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.*.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.{$id}.deployments.*", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -436,12 +503,15 @@ class WebhooksCustomServerTest extends Scope /** * @depends testCreateDeployment */ - public function testUpdateDeployment($data):array + public function testUpdateDeployment($data): array { + $id = $data['functionId'] ?? ''; + $deploymentId = $data['deploymentId'] ?? ''; + /** * Test for SUCCESS */ - $response = $this->client->call(Client::METHOD_PATCH, '/functions/'.$data['functionId'].'/deployments/'.$data['deploymentId'], array_merge([ + $response = $this->client->call(Client::METHOD_PATCH, '/functions/' . $id . '/deployments/' . $deploymentId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); @@ -457,7 +527,16 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'functions.deployments.update'); + $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('functions.*.deployments.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('functions.*.deployments.*.update', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.*.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.*.deployments.{$deploymentId}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.{$id}.deployments.*", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.{$id}.deployments.*.update", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -472,17 +551,20 @@ class WebhooksCustomServerTest extends Scope /** * @depends testUpdateDeployment */ - public function testExecutions($data):array + public function testExecutions($data): array { + $id = $data['functionId'] ?? ''; + /** * Test for SUCCESS */ - - $execution = $this->client->call(Client::METHOD_POST, '/functions/'.$data['functionId'].'/executions', array_merge([ + $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $id . '/executions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), []); + $executionId = $execution['body']['$id'] ?? ''; + $this->assertEquals($execution['headers']['status-code'], 201); $this->assertNotEmpty($execution['body']['$id']); @@ -491,12 +573,21 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'functions.executions.create'); + $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('functions.*.executions.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('functions.*.executions.*.create', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.*.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.*.executions.{$executionId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.{$id}.executions.*", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.{$id}.executions.*.create", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.{$id}.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.{$id}.executions.{$executionId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); - // wait for timeout function to complete (sleep(5);) + // wait for timeout function to complete sleep(10); $webhook = $this->getLastRequest(); @@ -504,7 +595,16 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'functions.executions.update'); + $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('functions.*.executions.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('functions.*.executions.*.update', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.*.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.*.executions.{$executionId}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.{$id}.executions.*", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.{$id}.executions.*.update", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.{$id}.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.{$id}.executions.{$executionId}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -519,12 +619,14 @@ class WebhooksCustomServerTest extends Scope /** * @depends testExecutions */ - public function testDeleteDeployment($data):array + public function testDeleteDeployment($data): array { + $id = $data['functionId'] ?? ''; + $deploymentId = $data['deploymentId'] ?? ''; /** * Test for SUCCESS */ - $deployment = $this->client->call(Client::METHOD_DELETE, '/functions/'.$data['functionId'].'/deployments/'.$data['deploymentId'], array_merge([ + $deployment = $this->client->call(Client::METHOD_DELETE, '/functions/' . $id . '/deployments/' . $deploymentId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -537,7 +639,16 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'functions.deployments.delete'); + $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('functions.*.deployments.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('functions.*.deployments.*.delete', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.*.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.*.deployments.{$deploymentId}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.{$id}.deployments.*", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.{$id}.deployments.*.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -552,12 +663,14 @@ class WebhooksCustomServerTest extends Scope /** * @depends testDeleteDeployment */ - public function testDeleteFunction($data):array + public function testDeleteFunction($data): array { + $id = $data['functionId']; + /** * Test for SUCCESS */ - $function = $this->client->call(Client::METHOD_DELETE, '/functions/'.$data['functionId'], array_merge([ + $function = $this->client->call(Client::METHOD_DELETE, '/functions/' . $id, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -570,7 +683,10 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'functions.delete'); + $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('functions.*.delete', $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString("functions.{$id}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); diff --git a/tests/e2e/Services/Workers/WebhooksTest.php b/tests/e2e/Services/Workers/WebhooksTest.php deleted file mode 100644 index f859f98850..0000000000 --- a/tests/e2e/Services/Workers/WebhooksTest.php +++ /dev/null @@ -1,154 +0,0 @@ -client->call(Client::METHOD_POST, '/teams', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'teamId' => 'unique()', - 'name' => 'Project Test', - ]); - - $this->assertEquals(201, $team['headers']['status-code']); - $this->assertEquals('Project Test', $team['body']['name']); - $this->assertNotEmpty($team['body']['$id']); - - $response = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'projectId' => 'unique()', - 'name' => 'Project Test', - 'teamId' => $team['body']['$id'], - ]); - - $this->assertEquals(201, $response['headers']['status-code']); - $this->assertNotEmpty($response['body']['$id']); - $this->assertEquals('Project Test', $response['body']['name']); - $this->assertEquals($team['body']['$id'], $response['body']['teamId']); - $this->assertArrayHasKey('platforms', $response['body']); - $this->assertArrayHasKey('webhooks', $response['body']); - $this->assertArrayHasKey('keys', $response['body']); - - $projectId = $response['body']['$id']; - - /** - * Test for FAILURE - */ - $response = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'projectId' => 'unique()', - 'name' => '', - 'teamId' => $team['body']['$id'], - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - $response = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'name' => 'Project Test', - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - return ['projectId' => $projectId]; - } - - /** - * @depends testCreateProject - */ - public function testCreateWebhook($data): array - { - $id = (isset($data['projectId'])) ? $data['projectId'] : ''; - - $response = $this->client->call(Client::METHOD_POST, '/projects/'.$id.'/webhooks', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'name' => 'Webhook Worker Test', - 'events' => ['account.create', 'account.update.email'], - 'url' => 'http://request-catcher:5000/webhook', - 'security' => true, - 'httpUser' => 'username', - 'httpPass' => 'password', - ]); - - $this->assertEquals(201, $response['headers']['status-code']); - $this->assertNotEmpty($response['body']['$id']); - $this->assertContains('account.create', $response['body']['events']); - $this->assertContains('account.update.email', $response['body']['events']); - $this->assertCount(2, $response['body']['events']); - $this->assertEquals('http://request-catcher:5000/webhook', $response['body']['url']); - $this->assertIsBool($response['body']['security']); - $this->assertEquals(true, $response['body']['security']); - $this->assertEquals('username', $response['body']['httpUser']); - - $data = array_merge($data, ['webhookId' => $response['body']['$id']]); - - /** - * Test for FAILURE - */ - - return $data; - } - - /** - * @depends testCreateWebhook - */ - public function testCreateAccount($data) - { - $projectId = (isset($data['projectId'])) ? $data['projectId'] : ''; - $email = uniqid().'webhook.user@localhost.test'; - $password = 'password'; - $name = 'User Name'; - - /** - * Test for SUCCESS - */ - $response = $this->client->call(Client::METHOD_POST, '/account', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - ]), [ - 'userId' => 'unique()', - 'email' => $email, - 'password' => $password, - 'name' => $name, - ]); - - $this->assertEquals($response['headers']['status-code'], 201); - - $webhook = $this->getLastRequest(); - - $this->assertNotEmpty($webhook['data']); - $this->assertNotEmpty($webhook['data']['$id']); - $this->assertIsBool($webhook['data']['status']); - $this->assertIsNumeric($webhook['data']['registration']); - $this->assertEquals($webhook['data']['email'], $email); - $this->assertEquals($webhook['data']['name'], $name); - $this->assertIsBool($webhook['data']['emailVerification']); - $this->assertIsArray($webhook['data']['prefs']); - } -} \ No newline at end of file diff --git a/tests/unit/Event/EventTest.php b/tests/unit/Event/EventTest.php index 7900a73710..d653541ada 100644 --- a/tests/unit/Event/EventTest.php +++ b/tests/unit/Event/EventTest.php @@ -63,8 +63,8 @@ class EventTest extends TestCase $this->object->trigger(); - $this->assertEquals(null, $this->object->getParam('eventKey1')); - $this->assertEquals(null, $this->object->getParam('eventKey2')); + $this->assertEquals('eventValue1', $this->object->getParam('eventKey1')); + $this->assertEquals('eventValue2', $this->object->getParam('eventKey2')); $this->assertEquals(null, $this->object->getParam('eventKey3')); $this->assertEquals(\Resque::size($this->queue), 1); } @@ -111,15 +111,18 @@ class EventTest extends TestCase 'collectionId' => 'chapters', 'documentId' => 'prolog', ]); - $this->assertCount(8, $event); + $this->assertCount(10, $event); + $this->assertContains('collections.chapters.documents.prolog.create', $event); $this->assertContains('collections.chapters.documents.prolog', $event); $this->assertContains('collections.chapters.documents.*.create', $event); $this->assertContains('collections.chapters.documents.*', $event); + $this->assertContains('collections.chapters', $event); $this->assertContains('collections.*.documents.prolog.create', $event); $this->assertContains('collections.*.documents.prolog', $event); $this->assertContains('collections.*.documents.*.create', $event); $this->assertContains('collections.*.documents.*', $event); + $this->assertContains('collections.*', $event); try { $event = Event::generateEvents('collections.[collectionId].documents.[documentId].create', [ diff --git a/tests/unit/Messaging/MessagingTest.php b/tests/unit/Messaging/MessagingTest.php index a0cad0bae9..0d7dd2a2e6 100644 --- a/tests/unit/Messaging/MessagingTest.php +++ b/tests/unit/Messaging/MessagingTest.php @@ -202,7 +202,7 @@ class MessagingTest extends TestCase * Test Collection Level Permissions */ $result = Realtime::fromPayload( - event: 'database.documents.create', + event: 'collections.collection_id.documents.document_id.create', payload: new Document([ '$id' => 'test', '$collection' => 'collection', @@ -224,7 +224,7 @@ class MessagingTest extends TestCase * Test Document Level Permissions */ $result = Realtime::fromPayload( - event: 'database.documents.create', + event: 'collections.collection_id.documents.document_id.create', payload: new Document([ '$id' => 'test', '$collection' => 'collection', @@ -242,4 +242,51 @@ class MessagingTest extends TestCase $this->assertContains('role:all', $result['roles']); $this->assertNotContains('role:admin', $result['roles']); } + + public function testFromPayloadBucketLevelPermissions(): void + { + /** + * Test Collection Level Permissions + */ + $result = Realtime::fromPayload( + event: 'buckets.bucket_id.files.file_id.create', + payload: new Document([ + '$id' => 'test', + '$collection' => 'bucket', + '$read' => ['role:admin'], + '$write' => ['role:admin'] + ]), + bucket: new Document([ + '$id' => 'bucket', + '$read' => ['role:all'], + '$write' => ['role:all'], + 'permission' => 'bucket' + ]) + ); + + $this->assertContains('role:all', $result['roles']); + $this->assertNotContains('role:admin', $result['roles']); + + /** + * Test Document Level Permissions + */ + $result = Realtime::fromPayload( + event: 'buckets.bucket_id.files.file_id.create', + payload: new Document([ + '$id' => 'test', + '$collection' => 'bucket', + '$read' => ['role:all'], + '$write' => ['role:all'] + ]), + bucket: new Document([ + '$id' => 'bucket', + '$read' => ['role:admin'], + '$write' => ['role:admin'], + 'permission' => 'file' + ]) + ); + + $this->assertContains('role:all', $result['roles']); + $this->assertNotContains('role:admin', $result['roles']); + } } From 4799804a9ae0177fce16921206b49265fcb7356d Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Mon, 18 Apr 2022 18:22:36 +0200 Subject: [PATCH 06/35] tests: remove worker tests --- phpunit.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/phpunit.xml b/phpunit.xml index 2fbb75e221..9f5e42b84e 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -29,7 +29,6 @@ ./tests/e2e/Services/Storage ./tests/e2e/Services/Teams ./tests/e2e/Services/Users - ./tests/e2e/Services/Workers ./tests/e2e/Services/Webhooks ./tests/e2e/Services/Functions/FunctionsBase.php ./tests/e2e/Services/Functions/FunctionsCustomServerTest.php From feb85629835c300de23ef235377c23f271f71034 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Tue, 19 Apr 2022 11:30:42 +0200 Subject: [PATCH 07/35] style: fix some whitespace --- app/controllers/api/account.php | 60 ++++++++++++-------------------- app/controllers/api/database.php | 4 +-- 2 files changed, 25 insertions(+), 39 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 61a14bf571..228c1df8b9 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -124,13 +124,9 @@ App::post('/v1/account') ->setUser($user) ; - $usage - ->setParam('users.create', 1) - ; + $usage->setParam('users.create', 1); + $events->setParam('userId', $user->getId()); - $events - ->setParam('userId', $user->getId()) - ; $response->setStatusCode(Response::STATUS_CODE_CREATED); $response->dynamic($user, Response::MODEL_USER); }); @@ -587,9 +583,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') ; if (!Config::getParam('domainVerification')) { - $response - ->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)])) - ; + $response->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)])); } // Add keys for non-web platforms - TODO - add verification phase to aviod session sniffing @@ -857,9 +851,7 @@ App::put('/v1/account/sessions/magic-url') ; if (!Config::getParam('domainVerification')) { - $response - ->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)])) - ; + $response->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)])); } $protocol = $request->getProtocol(); @@ -1007,9 +999,7 @@ App::post('/v1/account/sessions/anonymous') ; if (!Config::getParam('domainVerification')) { - $response - ->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)])) - ; + $response->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)])); } $response @@ -1019,8 +1009,8 @@ App::post('/v1/account/sessions/anonymous') ; $countryName = (isset($countries[strtoupper($session->getAttribute('countryCode'))])) - ? $countries[strtoupper($session->getAttribute('countryCode'))] - : $locale->getText('locale.country.unknown'); + ? $countries[strtoupper($session->getAttribute('countryCode'))] + : $locale->getText('locale.country.unknown'); $session ->setAttribute('current', true) @@ -1097,9 +1087,8 @@ App::get('/v1/account') /** @var Utopia\Database\Document $user */ /** @var Appwrite\Stats\Stats $usage */ - $usage - ->setParam('users.read', 1) - ; + $usage->setParam('users.read', 1); + $response->dynamic($user, Response::MODEL_USER); }); @@ -1124,9 +1113,8 @@ App::get('/v1/account/prefs') $prefs = $user->getAttribute('prefs', new \stdClass()); - $usage - ->setParam('users.read', 1) - ; + $usage->setParam('users.read', 1); + $response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES); }); @@ -1163,9 +1151,8 @@ App::get('/v1/account/sessions') $sessions[$key] = $session; } - $usage - ->setParam('users.read', 1) - ; + $usage->setParam('users.read', 1); + $response->dynamic(new Document([ 'sessions' => $sessions, 'total' => count($sessions), @@ -1231,9 +1218,7 @@ App::get('/v1/account/logs') } - $usage - ->setParam('users.read', 1) - ; + $usage->setParam('users.read', 1); $response->dynamic(new Document([ 'total' => $audit->countLogsByUser($user->getId()), @@ -1267,24 +1252,22 @@ App::get('/v1/account/sessions/:sessionId') $sessions = $user->getAttribute('sessions', []); $sessionId = ($sessionId === 'current') - ? Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret) - : $sessionId; + ? Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret) + : $sessionId; foreach ($sessions as $session) {/** @var Document $session */ if ($sessionId == $session->getId()) { $countryName = (isset($countries[strtoupper($session->getAttribute('countryCode'))])) - ? $countries[strtoupper($session->getAttribute('countryCode'))] - : $locale->getText('locale.country.unknown'); + ? $countries[strtoupper($session->getAttribute('countryCode'))] + : $locale->getText('locale.country.unknown'); $session ->setAttribute('current', ($session->getAttribute('secret') == Auth::hash(Auth::$secret))) ->setAttribute('countryName', $countryName) ; - $usage - ->setParam('users.read', 1) - ; + $usage->setParam('users.read', 1); return $response->dynamic($session, Response::MODEL_SESSION); } @@ -1369,7 +1352,10 @@ App::patch('/v1/account/password') throw new Exception('Invalid credentials', 401, Exception::USER_INVALID_CREDENTIALS); } - $user = $dbForProject->updateDocument('users', $user->getId(), $user + $user = $dbForProject->updateDocument( + 'users', + $user->getId(), + $user ->setAttribute('password', Auth::passwordHash($password)) ->setAttribute('passwordUpdate', \time()) ); diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index a1bff4199c..4795a3e74a 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -694,8 +694,6 @@ App::delete('/v1/database/collections/:collectionId') ->setDocument($collection) ; - $usage->setParam('database.collections.delete', 1); - $events ->setParam('collectionId', $collection->getId()) ->setPayload($response->output($collection, Response::MODEL_COLLECTION)) @@ -706,6 +704,8 @@ App::delete('/v1/database/collections/:collectionId') ->setPayload($collection->getArrayCopy()) ; + $usage->setParam('database.collections.delete', 1); + $response->noContent(); }); From 033bbec261b5477ec9fbe979e26c770e0520ffb5 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Tue, 19 Apr 2022 15:13:55 +0200 Subject: [PATCH 08/35] fix: usage and build --- app/controllers/api/functions.php | 66 ++++++++++--------- app/tasks/maintenance.php | 54 +++++++-------- app/workers/builds.php | 57 ++++++++-------- src/Appwrite/Event/Build.php | 105 ++++++++++++++++++++++++++++++ src/Appwrite/Event/Delete.php | 27 ++++++++ 5 files changed, 215 insertions(+), 94 deletions(-) create mode 100644 src/Appwrite/Event/Build.php diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index b8038676ba..da9232dabb 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -2,7 +2,7 @@ use Ahc\Jwt\JWT; use Appwrite\Auth\Auth; -use Appwrite\Event\Event; +use Appwrite\Event\Build; use Appwrite\Event\Func; use Appwrite\Event\Validator\Event as ValidatorEvent; use Appwrite\Extend\Exception; @@ -313,7 +313,7 @@ App::put('/v1/functions/:functionId') /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ /** @var Utopia\Database\Document $project */ - /** @var Appwrite\Auth\User $user */ + /** @var Utopia\Database\Document $user */ /** @var Appwrite\Event\Event $eventsInstance */ $function = $dbForProject->getDocument('functions', $functionId); @@ -339,14 +339,15 @@ App::put('/v1/functions/:functionId') ]))); if ($next && $schedule !== $original) { - ResqueScheduler::enqueueAt($next, Event::FUNCTIONS_QUEUE_NAME, Event::FUNCTIONS_CLASS_NAME, [ - 'projectId' => $project->getId(), - 'webhooks' => $project->getAttribute('webhooks', []), - 'functionId' => $function->getId(), - 'userId' => $user->getId(), - 'executionId' => null, - 'trigger' => 'schedule', - ]); // Async task rescheduale + // Async task reschedule + $functionEvent = new Func(); + $functionEvent + ->setFunction($function) + ->setType('schedule') + ->setUser($user) + ->setProject($project); + + $functionEvent->schedule($next); } $eventsInstance->setParam('functionId', $function->getId()); @@ -408,13 +409,12 @@ App::patch('/v1/functions/:functionId/deployments/:deploymentId') ]))); if ($next) { // Init first schedule - ResqueScheduler::enqueueAt($next, 'v1-functions', 'FunctionsV1', [ - 'projectId' => $project->getId(), - 'webhooks' => $project->getAttribute('webhooks', []), - 'functionId' => $function->getId(), - 'executionId' => null, - 'trigger' => 'schedule', - ]); // Async task rescheduale + $functionEvent = new Func(); + $functionEvent + ->setType('schedule') + ->setFunction($function) + ->setProject($project); + $functionEvent->schedule($next); } $events @@ -497,7 +497,7 @@ App::post('/v1/functions/:functionId/deployments') /** @var Utopia\Database\Database $dbForProject */ /** @var Appwrite\Event\Event $usage */ /** @var Appwrite\Event\Event $events */ - /** @var Appwrite\Database\Document $project */ + /** @var Utopia\Database\Document $project */ /** @var Utopia\Storage\Device $deviceFunctions */ /** @var Utopia\Storage\Device $deviceLocal */ @@ -616,13 +616,14 @@ App::post('/v1/functions/:functionId/deployments') $deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment->setAttribute('size', $fileSize)->setAttribute('metadata', $metadata)); } - // Enqueue a message to start the build - Resque::enqueue(Event::BUILDS_QUEUE_NAME, Event::BUILDS_CLASS_NAME, [ - 'projectId' => $project->getId(), - 'resourceId' => $function->getId(), - 'deploymentId' => $deploymentId, - 'type' => BUILD_TYPE_DEPLOYMENT - ]); + // Start the build + $buildEvent = new Build(); + $buildEvent + ->setType(BUILD_TYPE_DEPLOYMENT) + ->setResource($function) + ->setDeployment($deployment) + ->setProject($project) + ->trigger(); $usage->setParam('storage', $deployment->getAttribute('size', 0)); } else { @@ -1150,13 +1151,14 @@ App::post('/v1/functions/:functionId/deployments/:deploymentId/builds/:buildId') ->setParam('functionId', $function->getId()) ->setParam('deploymentId', $deployment->getId()); - // Enqueue a message to start the build - Resque::enqueue(Event::BUILDS_QUEUE_NAME, Event::BUILDS_CLASS_NAME, [ - 'projectId' => $project->getId(), - 'resourceId' => $function->getId(), - 'deploymentId' => $deploymentId, - 'type' => BUILD_TYPE_RETRY - ]); + // Retry the build + $buildEvent = new Build(); + $buildEvent + ->setType(BUILD_TYPE_RETRY) + ->setResource($function) + ->setDeployment($deployment) + ->setProject($project) + ->trigger(); $response->noContent(); }); diff --git a/app/tasks/maintenance.php b/app/tasks/maintenance.php index 05a9607883..b53628e699 100644 --- a/app/tasks/maintenance.php +++ b/app/tasks/maintenance.php @@ -2,7 +2,7 @@ global $cli; -use Appwrite\Event\Event; +use Appwrite\Event\Delete; use Utopia\App; use Utopia\CLI\Console; @@ -15,53 +15,43 @@ $cli function notifyDeleteExecutionLogs(int $interval) { - Resque::enqueue(Event::DELETE_QUEUE_NAME, Event::DELETE_CLASS_NAME, [ - 'payload' => [ - 'type' => DELETE_TYPE_EXECUTIONS, - 'timestamp' => time() - $interval - ] - ]); + (new Delete()) + ->setType(DELETE_TYPE_EXECUTIONS) + ->setTimestamp(time() - $interval) + ->trigger(); } function notifyDeleteAbuseLogs(int $interval) { - Resque::enqueue(Event::DELETE_QUEUE_NAME, Event::DELETE_CLASS_NAME, [ - 'payload' => [ - 'type' => DELETE_TYPE_ABUSE, - 'timestamp' => time() - $interval - ] - ]); + (new Delete()) + ->setType(DELETE_TYPE_ABUSE) + ->setTimestamp(time() - $interval) + ->trigger(); } function notifyDeleteAuditLogs(int $interval) { - Resque::enqueue(Event::DELETE_QUEUE_NAME, Event::DELETE_CLASS_NAME, [ - 'payload' => [ - 'type' => DELETE_TYPE_AUDIT, - 'timestamp' => time() - $interval - ] - ]); + (new Delete()) + ->setType(DELETE_TYPE_AUDIT) + ->setTimestamp(time() - $interval) + ->trigger(); } function notifyDeleteUsageStats(int $interval30m, int $interval1d) { - Resque::enqueue(Event::DELETE_QUEUE_NAME, Event::DELETE_CLASS_NAME, [ - 'payload' => [ - 'type' => DELETE_TYPE_USAGE, - 'timestamp1d' => time() - $interval1d, - 'timestamp30m' => time() - $interval30m, - ] - ]); + (new Delete()) + ->setType(DELETE_TYPE_USAGE) + ->setTimestamp1d(time() - $interval1d) + ->setTimestamp30m(time() - $interval30m) + ->trigger(); } function notifyDeleteConnections() { - Resque::enqueue(Event::DELETE_QUEUE_NAME, Event::DELETE_CLASS_NAME, [ - 'payload' => [ - 'type' => DELETE_TYPE_REALTIME, - 'timestamp' => time() - 60 - ] - ]); + (new Delete()) + ->setType(DELETE_TYPE_REALTIME) + ->setTimestamp(time() - 60) + ->trigger(); } // # of days in seconds (1 day = 86400s) diff --git a/app/workers/builds.php b/app/workers/builds.php index 83197bdfa2..a665430965 100644 --- a/app/workers/builds.php +++ b/app/workers/builds.php @@ -13,43 +13,41 @@ use Utopia\Storage\Storage; use Utopia\Database\Document; use Utopia\Config\Config; -require_once __DIR__.'/../init.php'; +require_once __DIR__ . '/../init.php'; // Disable Auth since we already validate it in the API Authorization::disable(); Console::title('Builds V1 Worker'); -Console::success(APP_NAME.' build worker v1 has started'); +Console::success(APP_NAME . ' build worker v1 has started'); // TODO: Executor should return appropriate response codes. class BuildsV1 extends Worker { - /** - * @var Executor - */ - private $executor = null; + private ?Executor $executor = null; - public function getName(): string + public function getName(): string { return "builds"; } - public function init(): void { + public function init(): void + { $this->executor = new Executor(); } public function run(): void { $type = $this->args['type'] ?? ''; - $projectId = $this->args['projectId'] ?? ''; - $functionId = $this->args['resourceId'] ?? ''; - $deploymentId = $this->args['deploymentId'] ?? ''; + $project = new Document($this->args['project'] ?? []); + $resource = new Document($this->args['resource'] ?? []); + $deployment = new Document($this->args['deployment'] ?? []); switch ($type) { case BUILD_TYPE_DEPLOYMENT: case BUILD_TYPE_RETRY: - Console::info("Creating build for deployment: $deploymentId"); - $this->buildDeployment($projectId, $functionId, $deploymentId); + Console::info('Creating build for deployment: ' . $deployment->getId()); + $this->buildDeployment($project, $resource, $deployment); break; default: @@ -58,18 +56,16 @@ class BuildsV1 extends Worker } } - protected function buildDeployment(string $projectId, string $functionId, string $deploymentId) + protected function buildDeployment(Document $project, Document $function, Document $deployment) { - $dbForProject = $this->getProjectDB($projectId); - $dbForConsole = $this->getConsoleDB(); - $project = $dbForConsole->getDocument('projects', $projectId); + $dbForProject = $this->getProjectDB($project->getId()); - $function = $dbForProject->getDocument('functions', $functionId); + $function = $dbForProject->getDocument('functions', $function->getId()); if ($function->isEmpty()) { throw new Exception('Function not found', 404); } - $deployment = $dbForProject->getDocument('deployments', $deploymentId); + $deployment = $dbForProject->getDocument('deployments', $deployment->getId()); if ($deployment->isEmpty()) { throw new Exception('Deployment not found', 404); } @@ -91,7 +87,7 @@ class BuildsV1 extends Worker '$read' => [], '$write' => [], 'startTime' => $startTime, - 'deploymentId' => $deploymentId, + 'deploymentId' => $deployment->getId(), 'status' => 'processing', 'outputPath' => '', 'runtime' => $function->getAttribute('runtime'), @@ -103,7 +99,7 @@ class BuildsV1 extends Worker 'duration' => 0 ])); $deployment->setAttribute('buildId', $buildId); - $deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment); + $deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment); } else { $build = $dbForProject->getDocument('builds', $buildId); } @@ -149,13 +145,13 @@ class BuildsV1 extends Worker try { $response = $this->executor->createRuntime( - projectId: $projectId, - deploymentId: $deploymentId, + projectId: $project->getId(), + deploymentId: $deployment->getId(), entrypoint: $deployment->getAttribute('entrypoint'), source: $source, - destination: APP_STORAGE_BUILDS . "/app-$projectId", - vars: $vars, - runtime: $key, + destination: APP_STORAGE_BUILDS . "/app-{$project->getId()}", + vars: $vars, + runtime: $key, baseImage: $baseImage, workdir: '/usr/code', remove: true, @@ -179,7 +175,7 @@ class BuildsV1 extends Worker /** Set auto deploy */ if ($deployment->getAttribute('activate') === true) { $function->setAttribute('deployment', $deployment->getId()); - $function = $dbForProject->updateDocument('functions', $functionId, $function); + $function = $dbForProject->updateDocument('functions', $function->getId(), $function); } /** Update function schedule */ @@ -187,8 +183,7 @@ class BuildsV1 extends Worker $cron = (empty($function->getAttribute('deployment')) && !empty($schedule)) ? new CronExpression($schedule) : null; $next = (empty($function->getAttribute('deployment')) && !empty($schedule)) ? $cron->getNextRunDate()->format('U') : 0; $function->setAttribute('scheduleNext', (int)$next); - $function = $dbForProject->updateDocument('functions', $functionId, $function); - + $function = $dbForProject->updateDocument('functions', $function->getId(), $function); } catch (\Throwable $th) { $endtime = \time(); $build->setAttribute('endTime', $endtime); @@ -213,5 +208,7 @@ class BuildsV1 extends Worker } } - public function shutdown(): void {} + public function shutdown(): void + { + } } diff --git a/src/Appwrite/Event/Build.php b/src/Appwrite/Event/Build.php new file mode 100644 index 0000000000..f6ba54dfd6 --- /dev/null +++ b/src/Appwrite/Event/Build.php @@ -0,0 +1,105 @@ +resource = $resource; + + return $this; + } + + /** + * Returns set resource document for the build event. + * + * @return null|\Utopia\Database\Document + */ + public function getResource(): ?Document + { + return $this->resource; + } + + /** + * Sets deployment for the build event. + * + * @param \Utopia\Database\Document $execution + * @return self + */ + public function setDeployment(Document $deployment): self + { + $this->deployment = $deployment; + + return $this; + } + + /** + * Returns set deployment for the build event. + * + * @return null|\Utopia\Database\Document + */ + public function getDeployment(): ?Document + { + return $this->deployment; + } + + /** + * Sets type for the build event. + * + * @param string $type Can be `BUILD_TYPE_DEPLOYMENT` or `BUILD_TYPE_RETRY`. + * @return self + */ + public function setType(string $type): self + { + $this->type = $type; + + return $this; + } + + /** + * Returns set type for the function event. + * + * @return string + */ + public function getType(): string + { + return $this->type; + } + + /** + * Executes the function event and sends it to the functions worker. + * + * @return string|bool + * @throws \InvalidArgumentException + */ + public function trigger(): string|bool + { + return Resque::enqueue($this->queue, $this->class, [ + 'project' => $this->project, + 'resource' => $this->resource, + 'deployment' => $this->deployment, + 'type' => $this->type + ]); + } +} diff --git a/src/Appwrite/Event/Delete.php b/src/Appwrite/Event/Delete.php index 8b02cbd89f..742a759e09 100644 --- a/src/Appwrite/Event/Delete.php +++ b/src/Appwrite/Event/Delete.php @@ -8,6 +8,9 @@ use Utopia\Database\Document; class Delete extends Event { protected string $type = ''; + protected ?int $timestamp = null; + protected ?int $timestamp1d = null; + protected ?int $timestamp30m = null; protected ?Document $document = null; public function __construct() @@ -38,6 +41,27 @@ class Delete extends Event return $this->type; } + public function setTimestamp(int $timestamp): self + { + $this->timestamp = $timestamp; + + return $this; + } + + public function setTimestamp1d(int $timestamp): self + { + $this->timestamp1d = $timestamp; + + return $this; + } + + public function setTimestamp30m(int $timestamp): self + { + $this->timestamp30m = $timestamp; + + return $this; + } + /** * Sets the document for the delete event. * @@ -73,6 +97,9 @@ class Delete extends Event 'project' => $this->project, 'type' => $this->type, 'document' => $this->document, + 'timestamp' => $this->timestamp, + 'timestamp1d' => $this->timestamp1d, + 'timestamp30m' => $this->timestamp30m ]); } } From d85c341875170de9f73733f3a74a6f5f0f46a162 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Wed, 20 Apr 2022 15:34:55 +0200 Subject: [PATCH 09/35] fix: events config --- app/config/events.php | 481 ++++++++---------- src/Appwrite/Event/Validator/Event.php | 73 +-- .../Event/Validator/EventValidatorTest.php | 10 + 3 files changed, 235 insertions(+), 329 deletions(-) diff --git a/app/config/events.php b/app/config/events.php index 9a5fecfbb0..d6f385d5f1 100644 --- a/app/config/events.php +++ b/app/config/events.php @@ -7,279 +7,220 @@ use Appwrite\Utopia\Response; return [ - 'account.create' => [ - 'description' => 'This event triggers when the account is created.', - 'model' => Response::MODEL_USER, - 'note' => '', + 'users' => [ + '$model' => Response::MODEL_USER, + '$resource' => true, + '$description' => 'This event triggers on any user\'s event.', + 'sessions' => [ + '$model' => Response::MODEL_SESSION, + '$resource' => true, + '$description' => 'This event triggers on any user\'s sessions event.', + 'create' => [ + '$description' => 'This event triggers when a session for a user is created.', + ], + 'delete' => [ + '$description' => 'This event triggers when a session for a user is deleted.' + ], + ], + 'recovery' => [ + '$model' => Response::MODEL_TOKEN, + '$resource' => true, + '$description' => 'This event triggers on any user\'s recovery token event.', + 'create' => [ + '$description' => 'This event triggers when a recovery token for a user is created.', + ], + 'update' => [ + '$description' => 'This event triggers when a recovery token for a user is validated.' + ], + ], + 'verification' => [ + '$model' => Response::MODEL_TOKEN, + '$resource' => true, + '$description' => 'This event triggers on any user\'s verification token event.', + 'create' => [ + '$description' => 'This event triggers when a verification token for a user is created.', + ], + 'update' => [ + '$description' => 'This event triggers when a verification token for a user is validated.' + ], + ], + 'create' => [ + '$description' => 'This event triggers when a user is created.' + ], + 'delete' => [ + '$description' => 'This event triggers when a user is deleted.', + ], + 'update' => [ + '$description' => 'This event triggers when a user is updated.', + 'email' => [ + '$description' => 'This event triggers when a user\'s email address is updated.', + ], + 'name' => [ + '$description' => 'This event triggers when a user\'s name is updated.', + ], + 'password' => [ + '$description' => 'This event triggers when a user\'s password is updated.', + ], + 'status' => [ + '$description' => 'This event triggers when a user\'s status is updated.', + ], + 'prefs' => [ + '$description' => 'This event triggers when a user\'s preferences is updated.', + ], + ] ], - 'account.update.email' => [ - 'description' => 'This event triggers when the account email address is updated.', - 'model' => Response::MODEL_USER, - 'note' => '', + 'collections' => [ + '$model' => Response::MODEL_COLLECTION, + '$resource' => true, + '$description' => 'This event triggers on any collection event.', + 'documents' => [ + '$model' => Response::MODEL_DOCUMENT, + '$resource' => true, + '$description' => 'This event triggers on any documents event.', + 'create' => [ + '$description' => 'This event triggers when a document is created.', + ], + 'delete' => [ + '$description' => 'This event triggers when a document is deleted.' + ], + 'update' => [ + '$description' => 'This event triggers when a document is updated.' + ], + ], + 'indexes' => [ + '$model' => Response::MODEL_INDEX, + '$resource' => true, + '$description' => 'This event triggers on any indexes event.', + 'create' => [ + '$description' => 'This event triggers when an index is created.', + ], + 'delete' => [ + '$description' => 'This event triggers when an index is deleted.' + ] + ], + 'attributes' => [ + '$model' => Response::MODEL_ATTRIBUTE, + '$resource' => true, + '$description' => 'This event triggers on any attributes event.', + 'create' => [ + '$description' => 'This event triggers when an attribute is created.', + ], + 'delete' => [ + '$description' => 'This event triggers when an attribute is deleted.' + ] + ], + 'create' => [ + '$description' => 'This event triggers when a collection is created.' + ], + 'delete' => [ + '$description' => 'This event triggers when a collection is deleted.', + ], + 'update' => [ + '$description' => 'This event triggers when a collection is updated.', + ] ], - 'account.update.name' => [ - 'description' => 'This event triggers when the account name is updated.', - 'model' => Response::MODEL_USER, - 'note' => '', + + 'buckets' => [ + '$model' => Response::MODEL_BUCKET, + '$resource' => true, + '$description' => 'This event triggers on any buckets event.', + 'files' => [ + '$model' => Response::MODEL_FILE, + '$resource' => true, + '$description' => 'This event triggers on any files event.', + 'create' => [ + '$description' => 'This event triggers when a file is created.', + ], + 'delete' => [ + '$description' => 'This event triggers when a file is deleted.' + ], + 'update' => [ + '$description' => 'This event triggers when a file is updated.' + ], + ], + 'create' => [ + '$description' => 'This event triggers when a bucket is created.' + ], + 'delete' => [ + '$description' => 'This event triggers when a bucket is deleted.', + ], + 'update' => [ + '$description' => 'This event triggers when a bucket is updated.', + ] ], - 'account.update.password' => [ - 'description' => 'This event triggers when the account password is updated.', - 'model' => Response::MODEL_USER, - 'note' => '', + + 'teams' => [ + '$model' => Response::MODEL_TEAM, + '$resource' => true, + '$description' => 'This event triggers on any teams event.', + 'memberships' => [ + '$model' => Response::MODEL_MEMBERSHIP, + '$resource' => true, + '$description' => 'This event triggers on any team memberships event.', + 'create' => [ + '$description' => 'This event triggers when a membership is created.', + ], + 'delete' => [ + '$description' => 'This event triggers when a membership is deleted.' + ], + 'update' => [ + '$description' => 'This event triggers when a membership is updated.', + 'status' => [ + '$description' => 'This event triggers when a team memberships status is updated.' + ] + ], + ], + 'create' => [ + '$description' => 'This event triggers when a bucket is created.' + ], + 'delete' => [ + '$description' => 'This event triggers when a bucket is deleted.', + ], + 'update' => [ + '$description' => 'This event triggers when a bucket is updated.', + ] ], - 'users.update.email' => [ - 'description' => 'This event triggers when the user email address is updated.', - 'model' => Response::MODEL_USER, - 'note' => '', - ], - 'users.update.name' => [ - 'description' => 'This event triggers when the user name is updated.', - 'model' => Response::MODEL_USER, - 'note' => '', - ], - 'users.update.password' => [ - 'description' => 'This event triggers when the user password is updated.', - 'model' => Response::MODEL_USER, - 'note' => '', - ], - 'account.update.prefs' => [ - 'description' => 'This event triggers when the account preferences are updated.', - 'model' => Response::MODEL_USER, - 'note' => '', - ], - 'account.recovery.create' => [ - 'description' => 'This event triggers when the account recovery token is created.', - 'model' => Response::MODEL_TOKEN, - 'note' => 'version >= 0.7', - ], - 'account.recovery.update' => [ - 'description' => 'This event triggers when the account recovery token is validated.', - 'model' => Response::MODEL_TOKEN, - 'note' => 'version >= 0.7', - ], - 'account.verification.create' => [ - 'description' => 'This event triggers when the account verification token is created.', - 'model' => Response::MODEL_TOKEN, - 'note' => 'version >= 0.7', - ], - 'account.verification.update' => [ - 'description' => 'This event triggers when the account verification token is validated.', - 'model' => Response::MODEL_TOKEN, - 'note' => 'version >= 0.7', - ], - 'account.delete' => [ - 'description' => 'This event triggers when the account is deleted.', - 'model' => Response::MODEL_USER, - 'note' => '', - ], - 'account.sessions.create' => [ - 'description' => 'This event triggers when the account session is created.', - 'model' => Response::MODEL_SESSION, - 'note' => '', - ], - 'account.sessions.delete' => [ - 'description' => 'This event triggers when the account session is deleted.', - 'model' => Response::MODEL_SESSION, - 'note' => '', - ], - 'account.sessions.update' => [ - 'description' => 'This event triggers when the account session is updated.', - 'model' => Response::MODEL_SESSION, - 'note' => '', - ], - 'database.collections.create' => [ - 'description' => 'This event triggers when a database collection is created.', - 'model' => Response::MODEL_COLLECTION, - 'note' => '', - ], - 'database.collections.update' => [ - 'description' => 'This event triggers when a database collection is updated.', - 'model' => Response::MODEL_COLLECTION, - 'note' => '', - ], - 'database.collections.delete' => [ - 'description' => 'This event triggers when a database collection is deleted.', - 'model' => Response::MODEL_COLLECTION, - 'note' => '', - ], - 'database.attributes.create' => [ - 'description' => 'This event triggers when a collection attribute is created.', - 'model' => Response::MODEL_ATTRIBUTE, - 'note' => '', - ], - 'database.attributes.delete' => [ - 'description' => 'This event triggers when a collection attribute is deleted.', - 'model' => Response::MODEL_ATTRIBUTE, - 'note' => '', - ], - 'database.indexes.create' => [ - 'description' => 'This event triggers when a collection index is created.', - 'model' => Response::MODEL_INDEX, - 'note' => '', - ], - 'database.indexes.delete' => [ - 'description' => 'This event triggers when a collection index is deleted.', - 'model' => Response::MODEL_INDEX, - 'note' => '', - ], - 'database.documents.create' => [ - 'description' => 'This event triggers when a database document is created.', - 'model' => Response::MODEL_DOCUMENT, - 'note' => '', - ], - 'database.documents.update' => [ - 'description' => 'This event triggers when a database document is updated.', - 'model' => Response::MODEL_DOCUMENT, - 'note' => '', - ], - 'database.documents.delete' => [ - 'description' => 'This event triggers when a database document is deleted.', - 'model' => Response::MODEL_DOCUMENT, - 'note' => '', - ], - 'functions.create' => [ - 'description' => 'This event triggers when a function is created.', - 'model' => Response::MODEL_FUNCTION, - 'note' => 'version >= 0.7', - ], - 'functions.update' => [ - 'description' => 'This event triggers when a function is updated.', - 'model' => Response::MODEL_FUNCTION, - 'note' => 'version >= 0.7', - ], - 'functions.delete' => [ - 'description' => 'This event triggers when a function is deleted.', - 'model' => Response::MODEL_ANY, - 'note' => 'version >= 0.7', - ], - 'functions.deployments.create' => [ - 'description' => 'This event triggers when a function delpoyment is created.', - 'model' => Response::MODEL_DEPLOYMENT, - 'note' => 'version >= 0.7', - ], - 'functions.deployments.update' => [ - 'description' => 'This event triggers when a function delpoyment is updated.', - 'model' => Response::MODEL_FUNCTION, - 'note' => 'version >= 0.7', - ], - 'functions.deployments.delete' => [ - 'description' => 'This event triggers when a function delpoyment is deleted.', - 'model' => Response::MODEL_ANY, - 'note' => 'version >= 0.7', - ], - 'functions.executions.create' => [ - 'description' => 'This event triggers when a function execution is created.', - 'model' => Response::MODEL_EXECUTION, - 'note' => 'version >= 0.7', - ], - 'functions.executions.update' => [ - 'description' => 'This event triggers when a function execution is updated.', - 'model' => Response::MODEL_EXECUTION, - 'note' => 'version >= 0.7', - ], - 'storage.files.create' => [ - 'description' => 'This event triggers when a storage file is created.', - 'model' => Response::MODEL_FILE, - 'note' => '', - ], - 'storage.files.update' => [ - 'description' => 'This event triggers when a storage file is updated.', - 'model' => Response::MODEL_FILE, - 'note' => '', - ], - 'storage.files.delete' => [ - 'description' => 'This event triggers when a storage file is deleted.', - 'model' => Response::MODEL_FILE, - 'note' => '', - ], - 'storage.buckets.create' => [ - 'description' => 'This event triggers when a storage bucket is created.', - 'model' => Response::MODEL_BUCKET, - 'note' => '', - ], - 'storage.buckets.update' => [ - 'description' => 'This event triggers when a storage bucket is updated.', - 'model' => Response::MODEL_BUCKET, - 'note' => '', - ], - 'storage.buckets.delete' => [ - 'description' => 'This event triggers when a storage bucket is deleted.', - 'model' => Response::MODEL_BUCKET, - 'note' => '', - ], - 'users.create' => [ - 'description' => 'This event triggers when a user is created from the users API.', - 'model' => Response::MODEL_USER, - 'note' => 'version >= 0.7', - ], - 'users.update.prefs' => [ - 'description' => 'This event triggers when a user preference is updated from the users API.', - 'model' => Response::MODEL_ANY, - 'note' => 'version >= 0.7', - ], - 'users.update.email' => [ - 'description' => 'This event triggers when the user email address is updated.', - 'model' => Response::MODEL_USER, - 'note' => 'version >= 0.10', - ], - 'users.update.name' => [ - 'description' => 'This event triggers when the user name is updated.', - 'model' => Response::MODEL_USER, - 'note' => 'version >= 0.10', - ], - 'users.update.password' => [ - 'description' => 'This event triggers when the user password is updated.', - 'model' => Response::MODEL_USER, - 'note' => 'version >= 0.10', - ], - 'users.update.status' => [ - 'description' => 'This event triggers when a user status is updated from the users API.', - 'model' => Response::MODEL_USER, - 'note' => 'version >= 0.7', - ], - 'users.delete' => [ - 'description' => 'This event triggers when a user is deleted from users API.', - 'model' => Response::MODEL_USER, - 'note' => 'version >= 0.7', - ], - 'users.sessions.delete' => [ - 'description' => 'This event triggers when a user session is deleted from users API.', - 'model' => Response::MODEL_SESSION, - 'note' => 'version >= 0.7', - ], - 'teams.create' => [ - 'description' => 'This event triggers when a team is created.', - 'model' => Response::MODEL_TEAM, - 'note' => 'version >= 0.7', - ], - 'teams.update' => [ - 'description' => 'This event triggers when a team is updated.', - 'model' => Response::MODEL_TEAM, - 'note' => 'version >= 0.7', - ], - 'teams.delete' => [ - 'description' => 'This event triggers when a team is deleted.', - 'model' => Response::MODEL_TEAM, - 'note' => 'version >= 0.7', - ], - 'teams.memberships.create' => [ - 'description' => 'This event triggers when a team memberships is created.', - 'model' => Response::MODEL_MEMBERSHIP, - 'note' => 'version >= 0.7', - ], - 'teams.memberships.update' => [ - 'description' => 'This event triggers when a team membership is updated.', - 'model' => Response::MODEL_MEMBERSHIP, - 'note' => 'version >= 0.8', - ], - 'teams.memberships.update.status' => [ - 'description' => 'This event triggers when a team memberships status is updated.', - 'model' => Response::MODEL_MEMBERSHIP, - 'note' => 'version >= 0.7', - ], - 'teams.memberships.delete' => [ - 'description' => 'This event triggers when a team memberships is deleted.', - 'model' => Response::MODEL_MEMBERSHIP, - 'note' => 'version >= 0.7', + + 'functions' => [ + '$model' => Response::MODEL_FUNCTION, + '$resource' => true, + '$description' => 'This event triggers on any functions event.', + 'deployments' => [ + '$model' => Response::MODEL_DEPLOYMENT, + '$resource' => true, + '$description' => 'This event triggers on any deployments event.', + 'create' => [ + '$description' => 'This event triggers when a deployment is created.', + ], + 'delete' => [ + '$description' => 'This event triggers when a deployment is deleted.' + ], + 'update' => [ + '$description' => 'This event triggers when a deployment is updated.' + ], + ], + 'executions' => [ + '$model' => Response::MODEL_EXECUTION, + '$resource' => true, + '$description' => 'This event triggers on any executions event.', + 'create' => [ + '$description' => 'This event triggers when an execution is created.', + ], + 'delete' => [ + '$description' => 'This event triggers when an execution is deleted.' + ], + 'update' => [ + '$description' => 'This event triggers when an execution is updated.' + ], + ], + 'create' => [ + '$description' => 'This event triggers when a function is created.' + ], + 'delete' => [ + '$description' => 'This event triggers when a function is deleted.', + ], + 'update' => [ + '$description' => 'This event triggers when a function is updated.', + ] ] ]; diff --git a/src/Appwrite/Event/Validator/Event.php b/src/Appwrite/Event/Validator/Event.php index 29dec12f39..d5ad85475d 100644 --- a/src/Appwrite/Event/Validator/Event.php +++ b/src/Appwrite/Event/Validator/Event.php @@ -2,60 +2,11 @@ namespace Appwrite\Event\Validator; +use Utopia\Config\Config; use Utopia\Validator; class Event extends Validator { - protected array $types = [ - 'users' => [ - 'subTypes' => [ - 'sessions', - 'recovery', - 'verification' - ], - 'attributes' => [ - 'email', - 'name', - 'password', - 'status', - 'prefs', - ] - ], - 'collections' => [ - 'subTypes' => [ - 'documents', - 'attributes', - 'indexes' - ] - ], - 'buckets' => [ - 'subTypes' => [ - 'files' - ] - ], - 'teams' => [ - 'subTypes' => [ - 'memberships' => [ - 'attributes' => [ - 'status' - ] - ] - ] - ], - 'functions' => [ - 'subTypes' => [ - 'deployments', - 'executions' - ] - ], - ]; - - protected array $actions = [ - 'create', - 'update', - 'delete' - ]; - /** * Get Description. * @@ -77,6 +28,7 @@ class Event extends Validator */ public function isValid($value): bool { + $events = Config::getParam('events', []); $parts = \explode('.', $value); $count = \count($parts); @@ -89,7 +41,7 @@ class Event extends Validator */ $type = $parts[0] ?? false; $resource = $parts[1] ?? false; - $hasSubResource = $count > 3 && \array_key_exists('subTypes', $this->types[$type]) && \in_array($parts[2], $this->types[$type]['subTypes']); + $hasSubResource = $count > 3 && ($events[$type]['$resource'] ?? false) && ($events[$type][$parts[2]]['$resource'] ?? false); if (!$type || !$resource) { return false; @@ -117,24 +69,27 @@ class Event extends Validator default => false }; - if ($action && !\in_array($action, $this->actions)) { - return false; - } - - if (!\in_array($type, \array_keys($this->types))) { + if (!\array_key_exists($type, $events)) { return false; } if ($subtype ?? false) { - if (!($subResource ?? false) || !\in_array($subType, $this->types[$type]['subTypes'])) { + if ($action && !\array_key_exists($action, $events[$type][$subType])) { + return false; + } + if (!($subResource ?? false) || !\array_key_exists($subType, $events[$type])) { + return false; + } + } else { + if ($action && !\array_key_exists($action, $events[$type])) { return false; } } if ($attribute ?? false) { if ( - (\array_key_exists('attributes', $this->types[$type]) && !\in_array($attribute, $this->types[$type]['attributes'])) || - (($subType ?? false) && \array_key_exists('attributes', $this->types[$type]['subTypes'][$subType]) && !\in_array($attribute, $this->types[$type]['subTypes'][$subType]['attributes'])) + !\array_key_exists($attribute, $events[$type][$action]) || + (($subType ?? false) && !\array_key_exists($attribute, $events[$type][$subType][$action])) ) { return false; } diff --git a/tests/unit/Event/Validator/EventValidatorTest.php b/tests/unit/Event/Validator/EventValidatorTest.php index 92898bd379..f0886ae5a7 100644 --- a/tests/unit/Event/Validator/EventValidatorTest.php +++ b/tests/unit/Event/Validator/EventValidatorTest.php @@ -4,6 +4,7 @@ namespace Appwrite\Tests; use Appwrite\Event\Validator\Event; use PHPUnit\Framework\TestCase; +use Utopia\Config\Config; class EventValidatorTest extends TestCase { @@ -11,6 +12,7 @@ class EventValidatorTest extends TestCase public function setUp(): void { + Config::load('events', __DIR__.'/../../../../app/config/events.php'); $this->object = new Event(); } @@ -20,6 +22,9 @@ class EventValidatorTest extends TestCase public function testValues() { + /** + * Test for SUCCESS + */ $this->assertTrue($this->object->isValid('users.*.create')); $this->assertTrue($this->object->isValid('users.torsten.update')); $this->assertTrue($this->object->isValid('users.torsten')); @@ -40,12 +45,17 @@ class EventValidatorTest extends TestCase $this->assertTrue($this->object->isValid('teams.*')); $this->assertTrue($this->object->isValid('users.*')); + /** + * Test for FAILURE + */ $this->assertFalse($this->object->isValid(false)); $this->assertFalse($this->object->isValid(null)); $this->assertFalse($this->object->isValid('')); + $this->assertFalse($this->object->isValid('unknown.*')); $this->assertFalse($this->object->isValid('collections')); $this->assertFalse($this->object->isValid('collections.*.unknown')); $this->assertFalse($this->object->isValid('collections.*.documents.*.unknown')); $this->assertFalse($this->object->isValid('users.torsten.unknown')); + $this->assertFalse($this->object->isValid('users.torsten.delete.email')); } } From ec22aa3dfe6cf4333598b5c5e073b4834f5e09dd Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Wed, 20 Apr 2022 15:38:43 +0200 Subject: [PATCH 10/35] style: remove whitespace --- app/config/events.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/config/events.php b/app/config/events.php index d6f385d5f1..2c2ded49d4 100644 --- a/app/config/events.php +++ b/app/config/events.php @@ -119,7 +119,6 @@ return [ '$description' => 'This event triggers when a collection is updated.', ] ], - 'buckets' => [ '$model' => Response::MODEL_BUCKET, '$resource' => true, @@ -148,7 +147,6 @@ return [ '$description' => 'This event triggers when a bucket is updated.', ] ], - 'teams' => [ '$model' => Response::MODEL_TEAM, '$resource' => true, @@ -180,7 +178,6 @@ return [ '$description' => 'This event triggers when a bucket is updated.', ] ], - 'functions' => [ '$model' => Response::MODEL_FUNCTION, '$resource' => true, From 379d074269fa331b78474eb772902a8b87e0791a Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Wed, 20 Apr 2022 16:03:40 +0200 Subject: [PATCH 11/35] fix: users audit logs --- app/controllers/api/users.php | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 23e725440e..a6743a0626 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -287,26 +287,8 @@ App::get('/v1/users/:userId/logs') } $audit = new Audit($dbForProject); - $auditEvents = [ - 'account.create', - 'account.delete', - 'account.update.name', - 'account.update.email', - 'account.update.password', - 'account.update.prefs', - 'account.sessions.create', - 'account.sessions.update', - 'account.sessions.delete', - 'account.recovery.create', - 'account.recovery.update', - 'account.verification.create', - 'account.verification.update', - 'teams.membership.create', - 'teams.membership.update', - 'teams.membership.delete', - ]; - $logs = $audit->getLogsByUserAndEvents($user->getId(), $auditEvents, $limit, $offset); + $logs = $audit->getLogsByUser($user->getId(), $limit, $offset); $output = []; @@ -354,7 +336,7 @@ App::get('/v1/users/:userId/logs') ; $response->dynamic(new Document([ - 'total' => $audit->countLogsByUserAndEvents($user->getId(), $auditEvents), + 'total' => $audit->countLogsByUser($user->getId()), 'logs' => $output, ]), Response::MODEL_LOG_LIST); }); From 95317d87b813affed8def950afb48b957eebf38c Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Sun, 8 May 2022 16:31:32 +0200 Subject: [PATCH 12/35] fix: event validator --- src/Appwrite/Event/Validator/Event.php | 13 ++++++++----- tests/unit/Event/Validator/EventValidatorTest.php | 2 ++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Appwrite/Event/Validator/Event.php b/src/Appwrite/Event/Validator/Event.php index d5ad85475d..ce0f3e0067 100644 --- a/src/Appwrite/Event/Validator/Event.php +++ b/src/Appwrite/Event/Validator/Event.php @@ -87,11 +87,14 @@ class Event extends Validator } if ($attribute ?? false) { - if ( - !\array_key_exists($attribute, $events[$type][$action]) || - (($subType ?? false) && !\array_key_exists($attribute, $events[$type][$subType][$action])) - ) { - return false; + if (($subType ?? false)) { + if (!\array_key_exists($attribute, $events[$type][$subType][$action])) { + return false; + } + } else { + if (!\array_key_exists($attribute, $events[$type][$action])) { + return false; + } } } diff --git a/tests/unit/Event/Validator/EventValidatorTest.php b/tests/unit/Event/Validator/EventValidatorTest.php index f0886ae5a7..e523999599 100644 --- a/tests/unit/Event/Validator/EventValidatorTest.php +++ b/tests/unit/Event/Validator/EventValidatorTest.php @@ -44,6 +44,7 @@ class EventValidatorTest extends TestCase $this->assertTrue($this->object->isValid('buckets.*')); $this->assertTrue($this->object->isValid('teams.*')); $this->assertTrue($this->object->isValid('users.*')); + $this->assertTrue($this->object->isValid('teams.*.memberships.*.update.status')); /** * Test for FAILURE @@ -57,5 +58,6 @@ class EventValidatorTest extends TestCase $this->assertFalse($this->object->isValid('collections.*.documents.*.unknown')); $this->assertFalse($this->object->isValid('users.torsten.unknown')); $this->assertFalse($this->object->isValid('users.torsten.delete.email')); + $this->assertFalse($this->object->isValid('teams.*.memberships.*.update.unknown')); } } From 07369cbfd256110b3ee3f107fbecbca82f338ceb Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Sun, 8 May 2022 16:31:50 +0200 Subject: [PATCH 13/35] feat: migration for 0.14 --- src/Appwrite/Migration/Version/V13.php | 216 ++++++++++++++++++++++ tests/unit/Migration/MigrationV13Test.php | 51 +++++ 2 files changed, 267 insertions(+) create mode 100644 src/Appwrite/Migration/Version/V13.php create mode 100644 tests/unit/Migration/MigrationV13Test.php diff --git a/src/Appwrite/Migration/Version/V13.php b/src/Appwrite/Migration/Version/V13.php new file mode 100644 index 0000000000..8255fa9d27 --- /dev/null +++ b/src/Appwrite/Migration/Version/V13.php @@ -0,0 +1,216 @@ +project->getAttribute('name') . ' (' . $this->project->getId() . ')'); + Console::info('Migrating Collections'); + $this->migrateCollections(); + Console::info('Migrating Documents'); + $this->forEachDocument([$this, 'fixDocument']); + } + + /** + * Migrate all Collections. + * + * @return void + */ + protected function migrateCollections(): void + { + foreach ($this->collections as $collection) { + $id = $collection['$id']; + + Console::log("- {$id}"); + switch ($id) { + case 'executions': + try { + /** + * Rename stdout to response + */ + $this->projectDB->renameAttribute($id, 'stdout', 'response'); + } catch (\Throwable $th) { + Console::warning("'stdout' from {$id}: {$th->getMessage()}"); + } + break; + case 'projects': + try { + /** + * Rename providers to authProviders + */ + $this->projectDB->renameAttribute($id, 'providers', 'authProviders'); + } catch (\Throwable $th) { + Console::warning("'providers' from {$id}: {$th->getMessage()}"); + } + break; + } + usleep(100000); + } + } + + /** + * Fix run on each document + * + * @param \Utopia\Database\Document $document + * @return \Utopia\Database\Document + */ + protected function fixDocument(Document $document) + { + switch ($document->getCollection()) { + case 'projects': + /** + * Bump Project version number. + */ + $document->setAttribute('version', '0.14.0'); + + break; + + case 'functions': + /** + * Migrate events. + */ + if (!empty($document->getAttribute('events'))) { + $document->setAttribute('events', $this->migrateEvents($document->getAttribute('events'))); + } + + break; + + case 'webhooks': + /** + * Migrate events. + */ + if (!empty($document->getAttribute('events'))) { + $document->setAttribute('events', $this->migrateEvents($document->getAttribute('events'))); + } + + break; + } + + return $document; + } + + public function migrateEvents(array $events): array + { + return array_filter(array_unique(array_map(function ($event) { + if (!in_array($event, $this->events)) return $event; + $parts = \explode('.', $event); + $first = array_shift($parts); + switch ($first) { + case 'account': + case 'users': + $first = 'users'; + + switch ($parts[0]) { + case 'recovery': + case 'sessions': + case 'verification': + $second = array_shift($parts); + return 'users.*.' . $second . '.*.' . implode('.', $parts); + + default: + return 'users.*.' . implode('.', $parts); + } + case 'functions': + switch ($parts[0]) { + case 'deployments': + case 'executions': + $second = array_shift($parts); + return 'functions.*.' . $second . '.*.' . implode('.', $parts); + + default: + return 'functions.*.' . implode('.', $parts); + } + case 'teams': + switch ($parts[0]) { + case 'memberships': + $second = array_shift($parts); + return 'teams.*.' . $second . '.*.' . implode('.', $parts); + + default: + return 'teams.*.' . implode('.', $parts); + } + case 'storage': + $second = array_shift($parts); + switch ($second) { + case 'buckets': + return 'buckets.*.' . implode('.', $parts); + case 'files': + return 'buckets.*.' . $second . '.*.' . implode('.', $parts); + } + case 'database': + $second = array_shift($parts); + switch ($second) { + case 'collections': + return 'collections.*.' . implode('.', $parts); + case 'documents': + case 'indexes': + case 'attributes': + return 'collections.*.' . $second . '.*.' . implode('.', $parts); + } + } + return ''; + }, $events))); + } +} diff --git a/tests/unit/Migration/MigrationV13Test.php b/tests/unit/Migration/MigrationV13Test.php new file mode 100644 index 0000000000..2315a4ff63 --- /dev/null +++ b/tests/unit/Migration/MigrationV13Test.php @@ -0,0 +1,51 @@ +migration = new V13(); + $reflector = new ReflectionClass('Appwrite\Migration\Version\V13'); + $this->method = $reflector->getMethod('fixDocument'); + $this->method->setAccessible(true); + } + + public function testMigrateFunctions() + { + $document = $this->fixDocument(new Document([ + '$id' => 'func', + '$collection' => 'functions', + 'events' => ['account.create', 'users.create'] + ])); + + $this->assertEquals($document->getAttribute('events'), ['users.*.create']); + } + + public function testMigrationWebhooks() + { + $document = $this->fixDocument(new Document([ + '$id' => 'webh', + '$collection' => 'webhooks', + 'events' => ['account.create', 'users.create'] + ])); + + $this->assertEquals($document->getAttribute('events'), ['users.*.create']); + } + + public function testEventsConversion() + { + $migration = new V13(); + $events = $migration->migrateEvents($migration->events); + foreach ($events as $event) { + $this->assertTrue((new Event())->isValid($event), $event); + } + $this->assertCount(45, $events); + } +} From 18379c25b8b7950a3178634f297e2488cec237d5 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Sun, 8 May 2022 17:49:17 +0200 Subject: [PATCH 14/35] fix: merge issues --- app/controllers/api/account.php | 8 ++++++-- app/controllers/api/storage.php | 2 +- app/workers/deletes.php | 1 - 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 2bf31a001a..b5a7e30679 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -826,7 +826,11 @@ App::put('/v1/account/sessions/magic-url') $dbForProject->deleteDocument('tokens', $token); $dbForProject->deleteCachedDocument('users', $user->getId()); - $user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('sessions', $session, Document::SET_TYPE_APPEND)); + $user + ->setAttribute('emailVerification', true) + ->setAttribute('sessions', $session, Document::SET_TYPE_APPEND); + + $user = $dbForProject->updateDocument('users', $user->getId(), $user); if (false === $user) { throw new Exception('Failed saving user to DB', 500, Exception::GENERAL_SERVER_ERROR); @@ -1962,7 +1966,7 @@ App::put('/v1/account/recovery') $events ->setParam('userId', $profile->getId()) - ->setParam('tokenId', $recovery->getId()) + ->setParam('tokenId', $recoveryDocument->getId()) ; $response->dynamic($recoveryDocument, Response::MODEL_TOKEN); diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 0c06c75f56..4ac1e63905 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -238,7 +238,7 @@ App::put('/v1/storage/buckets/:bucketId') ->inject('audits') ->inject('usage') ->inject('events') - ->action(function (string $bucketId, string $name, string $permission, ?array $read, ?array $write, bool $enabled, ?int $maximumFileSize, array $allowedFileExtensions, bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $audits, Stats $usage, Event $events) { + ->action(function (string $bucketId, string $name, string $permission, ?array $read, ?array $write, bool $enabled, ?int $maximumFileSize, array $allowedFileExtensions, bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Audit $audits, Stats $usage, Event $events) { $bucket = $dbForProject->getDocument('buckets', $bucketId); if ($bucket->isEmpty()) { diff --git a/app/workers/deletes.php b/app/workers/deletes.php index 5867ad15b8..b06f2ffa0e 100644 --- a/app/workers/deletes.php +++ b/app/workers/deletes.php @@ -208,7 +208,6 @@ class DeletesV1 extends Worker */ $userId = $document->getId(); - $user = $this->getProjectDB($projectId)->getDocument('users', $userId); // Delete all sessions of this user from the sessions table and update the sessions field of the user record $this->deleteByGroup('sessions', [ From bf2301ee997131a9836e3e8707ab6bc6c55a5016 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Sun, 8 May 2022 17:51:38 +0200 Subject: [PATCH 15/35] chore: build js --- public/dist/scripts/app-all.js | 4 ++-- public/dist/scripts/app.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/public/dist/scripts/app-all.js b/public/dist/scripts/app-all.js index 10fd74576c..a973062efb 100644 --- a/public/dist/scripts/app-all.js +++ b/public/dist/scripts/app-all.js @@ -3838,8 +3838,8 @@ list["filters-"+filter.key]=params[key][i];}}}} return list;};let apply=function(params){let cached=container.get(name);cached=cached?cached.params:[];params=Object.assign(cached,params);container.set(name,{name:name,params:params,query:serialize(params),forward:parseInt(params.offset)+parseInt(params.limit),backward:parseInt(params.offset)-parseInt(params.limit),keys:flatten(params)},true,name);document.dispatchEvent(new CustomEvent(name+"-changed",{bubbles:false,cancelable:true}));};switch(element.tagName){case"INPUT":break;case"TEXTAREA":break;case"BUTTON":element.addEventListener("click",function(){apply(JSON.parse(expression.parse(element.dataset["params"]||"{}")));});break;case"FORM":element.addEventListener("input",function(){apply(form.toJson(element));});element.addEventListener("change",function(){apply(form.toJson(element));});element.addEventListener("reset",function(){setTimeout(function(){apply(form.toJson(element));},0);});events=events.trim().split(",");for(let y=0;y=distance)&&(distance>=0)){if(minLink){minLink.classList.remove('selected');} -console.log('old',minLink);minDistance=distance;minElement=title;minLink=links[i];minLink.classList.add('selected');console.log('new',minLink);}}};window.addEventListener('scroll',check);check();}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-oauth-custom",controller:function(element){let providers={"Microsoft":{"clientSecret":"oauth2MicrosoftClientSecret","tenantID":"oauth2MicrosoftTenantId"},"Apple":{"keyID":"oauth2AppleKeyId","teamID":"oauth2AppleTeamId","p8":"oauth2AppleP8"}} -let provider=element.getAttribute("data-forms-oauth-custom");if(!provider||!providers.hasOwnProperty(provider)){console.error("Provider for custom form not set or unkown")} +console.log('old',minLink);minDistance=distance;minElement=title;minLink=links[i];minLink.classList.add('selected');console.log('new',minLink);}}};window.addEventListener('scroll',check);check();}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-oauth-custom",controller:function(element){let providers={"Microsoft":{"clientSecret":"oauth2MicrosoftClientSecret","tenantID":"oauth2MicrosoftTenantId"},"Apple":{"keyID":"oauth2AppleKeyId","teamID":"oauth2AppleTeamId","p8":"oauth2AppleP8"},"Okta":{"clientSecret":"oauth2OktaClientSecret","oktaDomain":"oauth2OktaDomain","authorizationServerId":"oauth2OktaAuthorizationServerId"},"Auth0":{"clientSecret":"oauth2Auth0ClientSecret","auth0Domain":"oauth2Auth0Domain"}} +let provider=element.getAttribute("data-forms-oauth-custom");if(!provider||!providers.hasOwnProperty(provider)){console.error("Provider for custom form not set or unknown")} let config=providers[provider];element.addEventListener('change',sync);let elements={};for(const key in config){if(Object.hasOwnProperty.call(config,key)){elements[key]=document.getElementById(config[key]);elements[key].addEventListener('change',update);}} function update(){let json={};for(const key in elements){if(Object.hasOwnProperty.call(elements,key)){json[key]=elements[key].value}} element.value=JSON.stringify(json);} diff --git a/public/dist/scripts/app.js b/public/dist/scripts/app.js index 7240e243c9..454c8586e3 100644 --- a/public/dist/scripts/app.js +++ b/public/dist/scripts/app.js @@ -785,8 +785,8 @@ list["filters-"+filter.key]=params[key][i];}}}} return list;};let apply=function(params){let cached=container.get(name);cached=cached?cached.params:[];params=Object.assign(cached,params);container.set(name,{name:name,params:params,query:serialize(params),forward:parseInt(params.offset)+parseInt(params.limit),backward:parseInt(params.offset)-parseInt(params.limit),keys:flatten(params)},true,name);document.dispatchEvent(new CustomEvent(name+"-changed",{bubbles:false,cancelable:true}));};switch(element.tagName){case"INPUT":break;case"TEXTAREA":break;case"BUTTON":element.addEventListener("click",function(){apply(JSON.parse(expression.parse(element.dataset["params"]||"{}")));});break;case"FORM":element.addEventListener("input",function(){apply(form.toJson(element));});element.addEventListener("change",function(){apply(form.toJson(element));});element.addEventListener("reset",function(){setTimeout(function(){apply(form.toJson(element));},0);});events=events.trim().split(",");for(let y=0;y=distance)&&(distance>=0)){if(minLink){minLink.classList.remove('selected');} -console.log('old',minLink);minDistance=distance;minElement=title;minLink=links[i];minLink.classList.add('selected');console.log('new',minLink);}}};window.addEventListener('scroll',check);check();}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-oauth-custom",controller:function(element){let providers={"Microsoft":{"clientSecret":"oauth2MicrosoftClientSecret","tenantID":"oauth2MicrosoftTenantId"},"Apple":{"keyID":"oauth2AppleKeyId","teamID":"oauth2AppleTeamId","p8":"oauth2AppleP8"}} -let provider=element.getAttribute("data-forms-oauth-custom");if(!provider||!providers.hasOwnProperty(provider)){console.error("Provider for custom form not set or unkown")} +console.log('old',minLink);minDistance=distance;minElement=title;minLink=links[i];minLink.classList.add('selected');console.log('new',minLink);}}};window.addEventListener('scroll',check);check();}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-oauth-custom",controller:function(element){let providers={"Microsoft":{"clientSecret":"oauth2MicrosoftClientSecret","tenantID":"oauth2MicrosoftTenantId"},"Apple":{"keyID":"oauth2AppleKeyId","teamID":"oauth2AppleTeamId","p8":"oauth2AppleP8"},"Okta":{"clientSecret":"oauth2OktaClientSecret","oktaDomain":"oauth2OktaDomain","authorizationServerId":"oauth2OktaAuthorizationServerId"},"Auth0":{"clientSecret":"oauth2Auth0ClientSecret","auth0Domain":"oauth2Auth0Domain"}} +let provider=element.getAttribute("data-forms-oauth-custom");if(!provider||!providers.hasOwnProperty(provider)){console.error("Provider for custom form not set or unknown")} let config=providers[provider];element.addEventListener('change',sync);let elements={};for(const key in config){if(Object.hasOwnProperty.call(config,key)){elements[key]=document.getElementById(config[key]);elements[key].addEventListener('change',update);}} function update(){let json={};for(const key in elements){if(Object.hasOwnProperty.call(elements,key)){json[key]=elements[key].value}} element.value=JSON.stringify(json);} From 3e80b3e6102033700568918b02b0b7ed4a460816 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Sun, 8 May 2022 18:11:52 +0200 Subject: [PATCH 16/35] chore: update composer --- composer.json | 2 +- composer.lock | 52 +++++++++++++++++++++++++-------------------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/composer.json b/composer.json index 75bab2aed7..71a810ac77 100644 --- a/composer.json +++ b/composer.json @@ -45,7 +45,7 @@ "utopia-php/cache": "0.6.*", "utopia-php/cli": "0.12.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "0.15.*", + "utopia-php/database": "0.16.*", "utopia-php/locale": "0.4.*", "utopia-php/registry": "0.5.*", "utopia-php/preloader": "0.2.*", diff --git a/composer.lock b/composer.lock index ea07e51fea..35f6d2135b 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": "78c5402d7bf745469d0063a9bc955df0", + "content-hash": "23a43ddfd26689ff154463410f7da3a4", "packages": [ { "name": "adhocore/jwt", @@ -2133,16 +2133,16 @@ }, { "name": "utopia-php/database", - "version": "0.15.5", + "version": "0.16.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "6507b58ef3e22703b9df68d3dbd5e822d5bb023f" + "reference": "7c484e2f71c551d2f8f43bf721938af3726c7455" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/6507b58ef3e22703b9df68d3dbd5e822d5bb023f", - "reference": "6507b58ef3e22703b9df68d3dbd5e822d5bb023f", + "url": "https://api.github.com/repos/utopia-php/database/zipball/7c484e2f71c551d2f8f43bf721938af3726c7455", + "reference": "7c484e2f71c551d2f8f43bf721938af3726c7455", "shasum": "" }, "require": { @@ -2190,9 +2190,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.15.5" + "source": "https://github.com/utopia-php/database/tree/0.16.0" }, - "time": "2022-04-04T13:42:00+00:00" + "time": "2022-05-08T16:07:02+00:00" }, { "name": "utopia-php/domains", @@ -3551,16 +3551,16 @@ }, { "name": "matthiasmullie/minify", - "version": "1.3.67", + "version": "1.3.68", "source": { "type": "git", "url": "https://github.com/matthiasmullie/minify.git", - "reference": "acaee1b7ca3cd67a39d7f98673cacd7e4739a8d9" + "reference": "c00fb02f71b2ef0a5f53fe18c5a8b9aa30f48297" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/matthiasmullie/minify/zipball/acaee1b7ca3cd67a39d7f98673cacd7e4739a8d9", - "reference": "acaee1b7ca3cd67a39d7f98673cacd7e4739a8d9", + "url": "https://api.github.com/repos/matthiasmullie/minify/zipball/c00fb02f71b2ef0a5f53fe18c5a8b9aa30f48297", + "reference": "c00fb02f71b2ef0a5f53fe18c5a8b9aa30f48297", "shasum": "" }, "require": { @@ -3609,7 +3609,7 @@ ], "support": { "issues": "https://github.com/matthiasmullie/minify/issues", - "source": "https://github.com/matthiasmullie/minify/tree/1.3.67" + "source": "https://github.com/matthiasmullie/minify/tree/1.3.68" }, "funding": [ { @@ -3617,7 +3617,7 @@ "type": "github" } ], - "time": "2022-03-24T08:54:59+00:00" + "time": "2022-04-19T08:28:56+00:00" }, { "name": "matthiasmullie/path-converter", @@ -5711,16 +5711,16 @@ }, { "name": "symfony/console", - "version": "v6.0.7", + "version": "v6.0.8", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "70dcf7b2ca2ea08ad6ebcc475f104a024fb5632e" + "reference": "0d00aa289215353aa8746a31d101f8e60826285c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/70dcf7b2ca2ea08ad6ebcc475f104a024fb5632e", - "reference": "70dcf7b2ca2ea08ad6ebcc475f104a024fb5632e", + "url": "https://api.github.com/repos/symfony/console/zipball/0d00aa289215353aa8746a31d101f8e60826285c", + "reference": "0d00aa289215353aa8746a31d101f8e60826285c", "shasum": "" }, "require": { @@ -5786,7 +5786,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.0.7" + "source": "https://github.com/symfony/console/tree/v6.0.8" }, "funding": [ { @@ -5802,7 +5802,7 @@ "type": "tidelift" } ], - "time": "2022-03-31T17:18:25+00:00" + "time": "2022-04-20T15:01:42+00:00" }, { "name": "symfony/polyfill-intl-grapheme", @@ -6136,16 +6136,16 @@ }, { "name": "symfony/string", - "version": "v6.0.3", + "version": "v6.0.8", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "522144f0c4c004c80d56fa47e40e17028e2eefc2" + "reference": "ac0aa5c2282e0de624c175b68d13f2c8f2e2649d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/522144f0c4c004c80d56fa47e40e17028e2eefc2", - "reference": "522144f0c4c004c80d56fa47e40e17028e2eefc2", + "url": "https://api.github.com/repos/symfony/string/zipball/ac0aa5c2282e0de624c175b68d13f2c8f2e2649d", + "reference": "ac0aa5c2282e0de624c175b68d13f2c8f2e2649d", "shasum": "" }, "require": { @@ -6201,7 +6201,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.0.3" + "source": "https://github.com/symfony/string/tree/v6.0.8" }, "funding": [ { @@ -6217,7 +6217,7 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:55:41+00:00" + "time": "2022-04-22T08:18:02+00:00" }, { "name": "textalk/websocket", @@ -6576,5 +6576,5 @@ "platform-overrides": { "php": "8.0" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.1.0" } From 936fed3b136c6e09871488edcc248925fb4925af Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Sun, 8 May 2022 20:53:20 +0200 Subject: [PATCH 17/35] chore: update versions --- README-CN.md | 6 +++--- README.md | 6 +++--- app/init.php | 2 +- app/tasks/sdks.php | 2 +- src/Appwrite/Migration/Migration.php | 1 + 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/README-CN.md b/README-CN.md index cc8457fd55..826163ee2b 100644 --- a/README-CN.md +++ b/README-CN.md @@ -59,7 +59,7 @@ docker run -it --rm \ --volume /var/run/docker.sock:/var/run/docker.sock \ --volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \ --entrypoint="install" \ - appwrite/appwrite:0.13.4 + appwrite/appwrite:0.14.0 ``` ### Windows @@ -71,7 +71,7 @@ docker run -it --rm ^ --volume //var/run/docker.sock:/var/run/docker.sock ^ --volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^ --entrypoint="install" ^ - appwrite/appwrite:0.13.4 + appwrite/appwrite:0.14.0 ``` #### PowerShell @@ -81,7 +81,7 @@ docker run -it --rm , --volume /var/run/docker.sock:/var/run/docker.sock , --volume ${pwd}/appwrite:/usr/src/code/appwrite:rw , --entrypoint="install" , - appwrite/appwrite:0.13.4 + appwrite/appwrite:0.14.0 ``` 运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。 diff --git a/README.md b/README.md index c74b33cc2c..dea890c3a3 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ docker run -it --rm \ --volume /var/run/docker.sock:/var/run/docker.sock \ --volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \ --entrypoint="install" \ - appwrite/appwrite:0.13.4 + appwrite/appwrite:0.14.0 ``` ### Windows @@ -74,7 +74,7 @@ docker run -it --rm ^ --volume //var/run/docker.sock:/var/run/docker.sock ^ --volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^ --entrypoint="install" ^ - appwrite/appwrite:0.13.4 + appwrite/appwrite:0.14.0 ``` #### PowerShell @@ -84,7 +84,7 @@ docker run -it --rm , --volume /var/run/docker.sock:/var/run/docker.sock , --volume ${pwd}/appwrite:/usr/src/code/appwrite:rw , --entrypoint="install" , - appwrite/appwrite:0.13.4 + appwrite/appwrite:0.14.0 ``` Once the Docker installation completes, go to http://localhost to access the Appwrite console from your browser. Please note that on non-Linux native hosts, the server might take a few minutes to start after installation completes. diff --git a/app/init.php b/app/init.php index ad08dff40d..a0a211799f 100644 --- a/app/init.php +++ b/app/init.php @@ -74,7 +74,7 @@ const APP_LIMIT_ANTIVIRUS = 20000000; //20MB const APP_LIMIT_ENCRYPTION = 20000000; //20MB const APP_LIMIT_COMPRESSION = 20000000; //20MB const APP_CACHE_BUSTER = 304; -const APP_VERSION_STABLE = '0.13.4'; +const APP_VERSION_STABLE = '0.14.0'; const APP_DATABASE_ATTRIBUTE_EMAIL = 'email'; const APP_DATABASE_ATTRIBUTE_ENUM = 'enum'; const APP_DATABASE_ATTRIBUTE_IP = 'ip'; diff --git a/app/tasks/sdks.php b/app/tasks/sdks.php index 166eec4951..1b3b8411a1 100644 --- a/app/tasks/sdks.php +++ b/app/tasks/sdks.php @@ -30,7 +30,7 @@ $cli $production = ($git) ? (Console::confirm('Type "Appwrite" to push code to production git repos') == 'Appwrite') : false; $message = ($git) ? Console::confirm('Please enter your commit message:') : ''; - if(!in_array($version, ['0.6.x', '0.7.x', '0.8.x', '0.9.x', '0.10.x', '0.11.x', '0.12.x', '0.13.x', 'latest'])) { + if(!in_array($version, ['0.6.x', '0.7.x', '0.8.x', '0.9.x', '0.10.x', '0.11.x', '0.12.x', '0.13.x', '0.14.x', 'latest'])) { throw new Exception('Unknown version given'); } diff --git a/src/Appwrite/Migration/Migration.php b/src/Appwrite/Migration/Migration.php index 723f210b81..f669360614 100644 --- a/src/Appwrite/Migration/Migration.php +++ b/src/Appwrite/Migration/Migration.php @@ -40,6 +40,7 @@ abstract class Migration '0.13.2' => 'V12', '0.13.3' => 'V12', '0.13.4' => 'V12', + '0.14.0' => 'V13', ]; /** From a4702012054212e0e44a2dc4dcebb21a4930a0c4 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Mon, 9 May 2022 09:35:55 +0200 Subject: [PATCH 18/35] fix: realtime --- app/realtime.php | 52 ++++++++++++++++++++++-------------------------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/app/realtime.php b/app/realtime.php index f4ed7350ff..3f996b0bc0 100644 --- a/app/realtime.php +++ b/app/realtime.php @@ -55,10 +55,10 @@ $adapter $server = new Server($adapter); -$logError = function(Throwable $error, string $action) use ($register) { +$logError = function (Throwable $error, string $action) use ($register) { $logger = $register->get('logger'); - if($logger) { + if ($logger) { $version = App::getEnv('_APP_VERSION', 'UNKNOWN'); $log = new Log(); @@ -82,7 +82,7 @@ $logError = function(Throwable $error, string $action) use ($register) { $log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING); $responseCode = $logger->addLog($log); - Console::info('Realtime log pushed with status code: '.$responseCode); + Console::info('Realtime log pushed with status code: ' . $responseCode); } Console::error('[Error] Type: ' . get_class($error)); @@ -113,10 +113,10 @@ function getDatabase(Registry &$register, string $namespace) throw new Exception('Collection not ready'); } break; // leave loop if successful - } catch(\Exception $e) { + } catch (\Exception $e) { Console::warning("Database not ready. Retrying connection ({$attempts})..."); if ($attempts >= DATABASE_RECONNECT_MAX_ATTEMPTS) { - throw new \Exception('Failed to connect to database: '. $e->getMessage()); + throw new \Exception('Failed to connect to database: ' . $e->getMessage()); } sleep(DATABASE_RECONNECT_SLEEP); } @@ -129,7 +129,6 @@ function getDatabase(Registry &$register, string $namespace) $register->get('redisPool')->put($redis); } ]; - }; $server->onStart(function () use ($stats, $register, $containerId, &$statsDocument, $logError) { @@ -151,7 +150,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume 'timestamp' => time(), 'value' => '{}' ]); - $statsDocument = Authorization::skip(fn() => $database->createDocument('realtime', $document)); + $statsDocument = Authorization::skip(fn () => $database->createDocument('realtime', $document)); } catch (\Throwable $th) { call_user_func($logError, $th, "createWorkerDocument"); } finally { @@ -178,7 +177,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume ->setAttribute('timestamp', time()) ->setAttribute('value', json_encode($payload)); - Authorization::skip(fn() => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument)); + Authorization::skip(fn () => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument)); } catch (\Throwable $th) { call_user_func($logError, $th, "updateWorkerDocument"); } finally { @@ -203,9 +202,9 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, $payload = []; - $list = Authorization::skip(fn() => $database->find('realtime', [ - new Query('timestamp', Query::TYPE_GREATER, [(time() - 15)]) - ])); + $list = Authorization::skip(fn () => $database->find('realtime', [ + new Query('timestamp', Query::TYPE_GREATER, [(time() - 15)]) + ])); /** * Aggregate stats across containers. @@ -299,19 +298,16 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, if ($realtime->hasSubscriber($projectId, 'user:' . $userId)) { $connection = array_key_first(reset($realtime->subscriptions[$projectId]['user:' . $userId])); - } else { - return; + [$database, $returnDatabase] = getDatabase($register, "_{$projectId}"); + + $user = $database->getDocument('users', $userId); + + $roles = Auth::getRoles($user); + + $realtime->subscribe($projectId, $connection, $roles, $realtime->connections[$connection]['channels']); + + call_user_func($returnDatabase); } - - [$database, $returnDatabase] = getDatabase($register, "_{$projectId}"); - - $user = $database->getDocument('users', $userId); - - $roles = Auth::getRoles($user); - - $realtime->subscribe($projectId, $connection, $roles, $realtime->connections[$connection]['channels']); - - call_user_func($returnDatabase); } $receivers = $realtime->getSubscribers($event); @@ -360,10 +356,10 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server, Console::info("Connection open (user: {$connection})"); - App::setResource('db', fn() => $db); - App::setResource('cache', fn() => $redis); - App::setResource('request', fn() => $request); - App::setResource('response', fn() => $response); + App::setResource('db', fn () => $db); + App::setResource('cache', fn () => $redis); + App::setResource('request', fn () => $request); + App::setResource('response', fn () => $response); try { /** @var \Utopia\Database\Document $user */ @@ -511,7 +507,7 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re } switch ($message['type']) { - /** + /** * This type is used to authenticate. */ case 'authentication': From fdb64de6169a4ae90c7410637f92f5c895e49e66 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Mon, 9 May 2022 14:36:29 +0200 Subject: [PATCH 19/35] docs: add comments to events passing --- app/controllers/shared/api.php | 1 + app/workers/audits.php | 28 ++++++++++++++++++---------- app/workers/builds.php | 18 +++++++++++++++--- app/workers/database.php | 28 ++++++++++++++++++++++++---- app/workers/functions.php | 7 ++++++- 5 files changed, 64 insertions(+), 18 deletions(-) diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index c92adaed09..e43fa46bb7 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -221,6 +221,7 @@ App::shutdown(function ($utopia, $request, $response, $project, $events, $audits $bucket = ($trigger && $trigger->getCollection() === 'buckets') ? $trigger : null; $target = Realtime::fromPayload( + // Pass first, most verbose event pattern event: $allEvents[0], payload: $payload, project: $project, diff --git a/app/workers/audits.php b/app/workers/audits.php index 2a0a85f6f4..696eb6df50 100644 --- a/app/workers/audits.php +++ b/app/workers/audits.php @@ -6,14 +6,15 @@ use Utopia\Audit\Audit; use Utopia\CLI\Console; use Utopia\Database\Document; -require_once __DIR__.'/../init.php'; +require_once __DIR__ . '/../init.php'; Console::title('Audits V1 Worker'); Console::success(APP_NAME . ' audits worker v1 has started'); class AuditsV1 extends Worker { - public function getName(): string { + public function getName(): string + { return "audits"; } @@ -36,16 +37,23 @@ class AuditsV1 extends Worker $userName = $user->getAttribute('name', ''); $userEmail = $user->getAttribute('email', ''); - $event = $events[0]; - $dbForProject = $this->getProjectDB($project->getId()); $audit = new Audit($dbForProject); - $audit->log($user->getId(), $event, $resource, $userAgent, $ip, '', [ - 'userName' => $userName, - 'userEmail' => $userEmail, - 'mode' => $mode, - 'data' => $payload, - ]); + $audit->log( + userId: $user->getId(), + // Pass first, most verbose event pattern + event: $events[0], + resource: $resource, + userAgent: $userAgent, + ip: $ip, + location: '', + data: [ + 'userName' => $userName, + 'userEmail' => $userEmail, + 'mode' => $mode, + 'data' => $payload, + ] + ); } public function shutdown(): void diff --git a/app/workers/builds.php b/app/workers/builds.php index a8136b0c98..c1affa5907 100644 --- a/app/workers/builds.php +++ b/app/workers/builds.php @@ -31,7 +31,8 @@ class BuildsV1 extends Worker return "builds"; } - public function init(): void { + public function init(): void + { $this->executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST')); } @@ -129,7 +130,13 @@ class BuildsV1 extends Worker 'functionId' => $function->getId(), 'deploymentId' => $deployment->getId() ]); - $target = Realtime::fromPayload($allEvents[0], $build, $project); + $target = Realtime::fromPayload( + // Pass first, most verbose event pattern + event: $allEvents[0], + payload: $build, + project: $project + ); + Realtime::send( projectId: 'console', payload: $build->getArrayCopy(), @@ -196,7 +203,12 @@ class BuildsV1 extends Worker /** * Send realtime Event */ - $target = Realtime::fromPayload($allEvents[0], $build, $project); + $target = Realtime::fromPayload( + // Pass first, most verbose event pattern + event: $allEvents[0], + payload: $build, + project: $project + ); Realtime::send( projectId: 'console', payload: $build->getArrayCopy(), diff --git a/app/workers/database.php b/app/workers/database.php index 40399dc776..a3451461cc 100644 --- a/app/workers/database.php +++ b/app/workers/database.php @@ -102,7 +102,12 @@ class DatabaseV1 extends Worker Console::error($th->getMessage()); $dbForProject->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'failed')); } finally { - $target = Realtime::fromPayload($events[0], $attribute, $project); + $target = Realtime::fromPayload( + // Pass first, most verbose event pattern + event: $events[0], + payload: $attribute, + project: $project + ); Realtime::send( projectId: 'console', @@ -154,7 +159,12 @@ class DatabaseV1 extends Worker Console::error($th->getMessage()); $dbForProject->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'stuck')); } finally { - $target = Realtime::fromPayload($events[0], $attribute, $project); + $target = Realtime::fromPayload( + // Pass first, most verbose event pattern + event: $events[0], + payload: $attribute, + project: $project + ); Realtime::send( projectId: 'console', @@ -255,7 +265,12 @@ class DatabaseV1 extends Worker Console::error($th->getMessage()); $dbForProject->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'failed')); } finally { - $target = Realtime::fromPayload($events[0], $index, $project); + $target = Realtime::fromPayload( + // Pass first, most verbose event pattern + event: $events[0], + payload: $index, + project: $project + ); Realtime::send( projectId: 'console', @@ -300,7 +315,12 @@ class DatabaseV1 extends Worker Console::error($th->getMessage()); $dbForProject->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'stuck')); } finally { - $target = Realtime::fromPayload($events[0], $index, $project); + $target = Realtime::fromPayload( + // Pass first, most verbose event pattern + event: $events[0], + payload: $index, + project: $project + ); Realtime::send( projectId: 'console', diff --git a/app/workers/functions.php b/app/workers/functions.php index fb2b683c29..4f7413188f 100644 --- a/app/workers/functions.php +++ b/app/workers/functions.php @@ -75,6 +75,7 @@ class FunctionsV1 extends Worker function: $function, dbForProject: $database, trigger: 'event', + // Pass first, most verbose event pattern event: $events[0], eventData: $payload, user: $user @@ -329,7 +330,11 @@ class FunctionsV1 extends Worker 'functionId' => $function->getId(), 'executionId' => $execution->getId() ]); - $target = Realtime::fromPayload($allEvents[0], $execution); + $target = Realtime::fromPayload( + // Pass first, most verbose event pattern + event: $allEvents[0], + payload: $execution + ); Realtime::send( projectId: 'console', payload: $execution->getArrayCopy(), From 57246b14d70afa398435235ecdfc02ebba9f50a5 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Mon, 9 May 2022 14:57:42 +0200 Subject: [PATCH 20/35] fix: add missing teams update audit --- app/controllers/api/teams.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index fe030fe279..c504fe40e1 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -189,7 +189,8 @@ App::put('/v1/teams/:teamId') ->inject('response') ->inject('dbForProject') ->inject('events') - ->action(function (string $teamId, string $name, Response $response, Database $dbForProject, Event $events) { + ->inject('audits') + ->action(function (string $teamId, string $name, Response $response, Database $dbForProject, Event $events, EventAudit $audits) { $team = $dbForProject->getDocument('teams', $teamId); @@ -203,6 +204,7 @@ App::put('/v1/teams/:teamId') ); $events->setParam('teamId', $team->getId()); + $audits->setResource('team/' . $team->getId()); $response->dynamic($team, Response::MODEL_TEAM); }); From 0bd62bf4f826ad1ce72fb8b3c37c5156f399800e Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Tue, 10 May 2022 14:08:40 +0200 Subject: [PATCH 21/35] fix: payload duplicates --- app/controllers/shared/api.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index e43fa46bb7..e4ab00c04b 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -196,7 +196,6 @@ App::shutdown(function ($utopia, $request, $response, $project, $events, $audits $events ->setClass(Event::FUNCTIONS_CLASS_NAME) ->setQueue(Event::FUNCTIONS_QUEUE_NAME) - ->setPayload($events->getPayload()) ->trigger(); /** @@ -205,9 +204,7 @@ App::shutdown(function ($utopia, $request, $response, $project, $events, $audits $events ->setClass(Event::WEBHOOK_CLASS_NAME) ->setQueue(Event::WEBHOOK_QUEUE_NAME) - ->setPayload($events->getPayload()) ->trigger(); - var_dump($events->getEvent()); /** * Trigger realtime. @@ -215,10 +212,10 @@ App::shutdown(function ($utopia, $request, $response, $project, $events, $audits if ($project->getId() !== 'console') { $allEvents = Event::generateEvents($events->getEvent(), $events->getParams()); $payload = new Document($events->getPayload()); - $trigger = $events->getContext() ?? false; + $context = $events->getContext() ?? false; - $collection = ($trigger && $trigger->getCollection() === 'collections') ? $trigger : null; - $bucket = ($trigger && $trigger->getCollection() === 'buckets') ? $trigger : null; + $collection = ($context && $context->getCollection() === 'collections') ? $context : null; + $bucket = ($context && $context->getCollection() === 'buckets') ? $context : null; $target = Realtime::fromPayload( // Pass first, most verbose event pattern From ec0e8adcc51a79e39fe8bf8e0ce8c5f88a44595e Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Tue, 10 May 2022 14:10:38 +0200 Subject: [PATCH 22/35] fix: mails project name fallback --- app/workers/mails.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/workers/mails.php b/app/workers/mails.php index aaedf8aeed..cd7cd81751 100644 --- a/app/workers/mails.php +++ b/app/workers/mails.php @@ -41,7 +41,7 @@ class MailsV1 extends Worker $type = $this->args['type']; $prefix = $this->getPrefix($type); $locale = new Locale($this->args['locale']); - $projectName = $project->getAttribute('name', ['[APP-NAME]']); + $projectName = $project->getAttribute('name', '[APP-NAME]'); if (!$this->doesLocaleExist($locale, $prefix)) { $locale->setDefault('en'); From 611d2d239c7b5c6ae8b8c6ec44d48ca9e7bcc9ca Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Tue, 10 May 2022 14:12:56 +0200 Subject: [PATCH 23/35] fix: webhook remove unnecessary logs --- app/workers/webhooks.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/workers/webhooks.php b/app/workers/webhooks.php index cea58fdf68..ca21d3f6a6 100644 --- a/app/workers/webhooks.php +++ b/app/workers/webhooks.php @@ -32,11 +32,6 @@ class WebhooksV1 extends Worker foreach ($project->getAttribute('webhooks', []) as $webhook) { if (array_intersect($webhook->getAttribute('events', []), $events)) { - Console::log(""); - Console::log(""); - Console::log(var_export($events, true)); - Console::log(""); - Console::log(""); $this->execute($events, $payload, $webhook, $user, $project); } } From 8012de5c4d0356967c60131c5388899055b6334d Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Tue, 10 May 2022 14:13:53 +0200 Subject: [PATCH 24/35] fix: rename webhook event header to plural --- 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 6495c513ba..ad1d018ece 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -709,6 +709,7 @@ App::delete('/v1/users/:userId/sessions') foreach ($sessions as $key => $session) { /** @var Document $session */ $dbForProject->deleteDocument('sessions', $session->getId()); + //TODO: fix this } $dbForProject->deleteCachedDocument('users', $user->getId()); From 04eeb8856c0961d7a21d6cf31b26087af79c19e8 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Tue, 10 May 2022 14:14:26 +0200 Subject: [PATCH 25/35] fix: rename webhook event header to plural --- app/workers/webhooks.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/workers/webhooks.php b/app/workers/webhooks.php index ca21d3f6a6..e44881c1b4 100644 --- a/app/workers/webhooks.php +++ b/app/workers/webhooks.php @@ -64,7 +64,7 @@ class WebhooksV1 extends Worker 'Content-Type: application/json', 'Content-Length: ' . \strlen($payload), 'X-' . APP_NAME . '-Webhook-Id: ' . $webhook->getId(), - 'X-' . APP_NAME . '-Webhook-Event: ' . implode(', ', $events), + 'X-' . APP_NAME . '-Webhook-Events: ' . implode(',', $events), 'X-' . APP_NAME . '-Webhook-Name: ' . $webhook->getAttribute('name', ''), 'X-' . APP_NAME . '-Webhook-User-Id: ' . $user->getId(), 'X-' . APP_NAME . '-Webhook-Project-Id: ' . $project->getId(), From 4e79d5fb67df7db4d8a3b2b8bd02daefebbd7fd1 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Tue, 10 May 2022 14:20:51 +0200 Subject: [PATCH 26/35] fix: remove array merge for documents --- app/workers/functions.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/workers/functions.php b/app/workers/functions.php index 4f7413188f..1a6ae838b1 100644 --- a/app/workers/functions.php +++ b/app/workers/functions.php @@ -151,9 +151,7 @@ class FunctionsV1 extends Worker $function = Authorization::skip(fn () => $database->updateDocument( 'functions', $function->getId(), - new Document(array_merge($function->getArrayCopy(), [ - 'scheduleNext' => (int)$next, - ])) + $function->setAttribute('scheduleNext', (int) $next) )); if ($function === false) { From 54dd80823288b57ca0639bfe5922988f21953dd9 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Tue, 10 May 2022 14:28:06 +0200 Subject: [PATCH 27/35] docs: fix audit class phpdocs --- src/Appwrite/Event/Audit.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Event/Audit.php b/src/Appwrite/Event/Audit.php index ad90e7cfa4..b2d34424ea 100644 --- a/src/Appwrite/Event/Audit.php +++ b/src/Appwrite/Event/Audit.php @@ -88,7 +88,7 @@ class Audit extends Event /** * Set IP for this audit event. * - * @param string $userAgent + * @param string $ip * @return self */ public function setIP(string $ip): self From 28588d83cff5c73bafe7d06cb706bdae330cf185 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Tue, 10 May 2022 14:31:20 +0200 Subject: [PATCH 28/35] docs: fix php docs in event classes --- src/Appwrite/Event/Build.php | 10 ++++------ src/Appwrite/Event/Certificate.php | 4 ++-- src/Appwrite/Event/Database.php | 8 ++++---- src/Appwrite/Event/Delete.php | 4 ++-- src/Appwrite/Event/Func.php | 8 ++++---- src/Appwrite/Event/Mail.php | 4 ++-- 6 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/Appwrite/Event/Build.php b/src/Appwrite/Event/Build.php index f6ba54dfd6..970de82653 100644 --- a/src/Appwrite/Event/Build.php +++ b/src/Appwrite/Event/Build.php @@ -2,9 +2,7 @@ namespace Appwrite\Event; -use DateTime; use Resque; -use ResqueScheduler; use Utopia\Database\Document; class Build extends Event @@ -21,7 +19,7 @@ class Build extends Event /** * Sets resource document for the build event. * - * @param \Utopia\Database\Document $function + * @param Document $resource * @return self */ public function setResource(Document $resource): self @@ -34,7 +32,7 @@ class Build extends Event /** * Returns set resource document for the build event. * - * @return null|\Utopia\Database\Document + * @return null|Document */ public function getResource(): ?Document { @@ -44,7 +42,7 @@ class Build extends Event /** * Sets deployment for the build event. * - * @param \Utopia\Database\Document $execution + * @param Document $deployment * @return self */ public function setDeployment(Document $deployment): self @@ -57,7 +55,7 @@ class Build extends Event /** * Returns set deployment for the build event. * - * @return null|\Utopia\Database\Document + * @return null|Document */ public function getDeployment(): ?Document { diff --git a/src/Appwrite/Event/Certificate.php b/src/Appwrite/Event/Certificate.php index b01fa71984..59c2ce9a76 100644 --- a/src/Appwrite/Event/Certificate.php +++ b/src/Appwrite/Event/Certificate.php @@ -19,7 +19,7 @@ class Certificate extends Event /** * Set domain for this certificates event. * - * @param \Utopia\Database\Document $domain + * @param Document $domain * @return self */ public function setDomain(Document $domain): self @@ -32,7 +32,7 @@ class Certificate extends Event /** * Returns the set domain for this certificate event. * - * @return null|\Utopia\Database\Document + * @return null|Document */ public function getDomain(): ?Document { diff --git a/src/Appwrite/Event/Database.php b/src/Appwrite/Event/Database.php index 28123e8a03..5e5e687857 100644 --- a/src/Appwrite/Event/Database.php +++ b/src/Appwrite/Event/Database.php @@ -41,7 +41,7 @@ class Database extends Event /** * Set the collection for this database event. * - * @param \Utopia\Database\Document $collection + * @param Document $collection * @return self */ public function setCollection(Document $collection): self @@ -54,7 +54,7 @@ class Database extends Event /** * Returns set collection for this event. * - * @return null|\Utopia\Database\Document + * @return null|Document */ public function getCollection(): ?Document { @@ -64,7 +64,7 @@ class Database extends Event /** * Set the document for this database event. * - * @param \Utopia\Database\Document $document + * @param Document $document * @return self */ public function setDocument(Document $document): self @@ -76,7 +76,7 @@ class Database extends Event /** * Returns set document for this database event. - * @return null|\Utopia\Database\Document + * @return null|Document */ public function getDocument(): ?Document { diff --git a/src/Appwrite/Event/Delete.php b/src/Appwrite/Event/Delete.php index 742a759e09..a8255cf3fe 100644 --- a/src/Appwrite/Event/Delete.php +++ b/src/Appwrite/Event/Delete.php @@ -65,7 +65,7 @@ class Delete extends Event /** * Sets the document for the delete event. * - * @param \Utopia\Database\Document $document + * @param Document $document * @return self */ public function setDocument(Document $document): self @@ -78,7 +78,7 @@ class Delete extends Event /** * Returns the set document for the delete event. * - * @return null|\Utopia\Database\Document + * @return null|Document */ public function getDocument(): ?Document { diff --git a/src/Appwrite/Event/Func.php b/src/Appwrite/Event/Func.php index 18f684c4a2..98652431f0 100644 --- a/src/Appwrite/Event/Func.php +++ b/src/Appwrite/Event/Func.php @@ -23,7 +23,7 @@ class Func extends Event /** * Sets function document for the function event. * - * @param \Utopia\Database\Document $function + * @param Document $function * @return self */ public function setFunction(Document $function): self @@ -36,7 +36,7 @@ class Func extends Event /** * Returns set function document for the function event. * - * @return null|\Utopia\Database\Document + * @return null|Document */ public function getFunction(): ?Document { @@ -46,7 +46,7 @@ class Func extends Event /** * Sets execution for the function event. * - * @param \Utopia\Database\Document $execution + * @param Document $execution * @return self */ public function setExecution(Document $execution): self @@ -59,7 +59,7 @@ class Func extends Event /** * Returns set execution for the function event. * - * @return null|\Utopia\Database\Document + * @return null|Document */ public function getExecution(): ?Document { diff --git a/src/Appwrite/Event/Mail.php b/src/Appwrite/Event/Mail.php index 28df2c6b89..b3325c00be 100644 --- a/src/Appwrite/Event/Mail.php +++ b/src/Appwrite/Event/Mail.php @@ -22,7 +22,7 @@ class Mail extends Event /** * Sets team for the mail event. * - * @param \Utopia\Database\Document $team + * @param Document $team * @return self */ public function setTeam(Document $team): self @@ -35,7 +35,7 @@ class Mail extends Event /** * Returns set team for the mail event. * - * @return null|\Utopia\Database\Document + * @return null|Document */ public function getTeam(): ?Document { From 0f89e281162aa412a6b230cb0d691a756a731702 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Tue, 10 May 2022 14:33:31 +0200 Subject: [PATCH 29/35] docs: fix php delete class phpdocs --- src/Appwrite/Event/Delete.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/Appwrite/Event/Delete.php b/src/Appwrite/Event/Delete.php index a8255cf3fe..1c2be4b013 100644 --- a/src/Appwrite/Event/Delete.php +++ b/src/Appwrite/Event/Delete.php @@ -41,6 +41,12 @@ class Delete extends Event return $this->type; } + /** + * Set timestamp. + * + * @param int $timestamp + * @return self + */ public function setTimestamp(int $timestamp): self { $this->timestamp = $timestamp; @@ -48,6 +54,12 @@ class Delete extends Event return $this; } + /** + * Set timestamp for 1 day interval. + * + * @param int $timestamp + * @return self + */ public function setTimestamp1d(int $timestamp): self { $this->timestamp1d = $timestamp; @@ -55,6 +67,12 @@ class Delete extends Event return $this; } + /** + * Sets timestamp for 30m interval. + * + * @param int $timestamp + * @return self + */ public function setTimestamp30m(int $timestamp): self { $this->timestamp30m = $timestamp; From 7dc5d0e2beec773517f460da5aed23580f817316 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Tue, 10 May 2022 15:03:34 +0200 Subject: [PATCH 30/35] fix: event class --- src/Appwrite/Event/Event.php | 55 +++++++++++++++++++++++--- src/Appwrite/Event/Validator/Event.php | 8 ++-- 2 files changed, 53 insertions(+), 10 deletions(-) diff --git a/src/Appwrite/Event/Event.php b/src/Appwrite/Event/Event.php index a8dd8f62d5..871c4c4f41 100644 --- a/src/Appwrite/Event/Event.php +++ b/src/Appwrite/Event/Event.php @@ -2,7 +2,6 @@ namespace Appwrite\Event; -use Exception; use InvalidArgumentException; use Resque; use Utopia\Database\Document; @@ -98,6 +97,12 @@ class Event return $this->event; } + /** + * Set project for this event. + * + * @param Document $project + * @return self + */ public function setProject(Document $project): self { $this->project = $project; @@ -105,11 +110,22 @@ class Event return $this; } + /** + * Get project for this event. + * + * @return Document + */ public function getProject(): Document { return $this->project; } + /** + * Set user for this event. + * + * @param Document $user + * @return self + */ public function setUser(Document $user): self { $this->user = $user; @@ -117,11 +133,22 @@ class Event return $this; } + /** + * Get project for this event. + * + * @return Document + */ public function getUser(): Document { return $this->user; } + /** + * Set payload for this event. + * + * @param Document $payload + * @return self + */ public function setPayload(array $payload): self { $this->payload = $payload; @@ -129,11 +156,22 @@ class Event return $this; } + /** + * Get payload for this event. + * + * @return Document + */ public function getPayload(): array { return $this->payload; } + /** + * Set context for this event. + * + * @param Document $context + * @return self + */ public function setContext(Document $context): self { $this->context = $context; @@ -141,6 +179,11 @@ class Event return $this; } + /** + * Get context for this event. + * + * @return Document + */ public function getContext(): ?Document { return $this->context; @@ -149,7 +192,7 @@ class Event /** * Set class used for this event. * @param string $class - * @return Event + * @return self */ public function setClass(string $class): self { @@ -173,7 +216,7 @@ class Event * * @param string $key * @param mixed $value - * @return Event + * @return self */ public function setParam(string $key, mixed $value): self { @@ -223,7 +266,7 @@ class Event /** * Resets event. * - * @return Event + * @return self */ public function reset(): self { @@ -244,7 +287,7 @@ class Event $count = \count($parts); /** - * Identify all sestions of the pattern. + * Identify all sections of the pattern. */ $type = $parts[0] ?? false; $resource = $parts[1] ?? false; @@ -291,7 +334,7 @@ class Event */ static function generateEvents(string $pattern, array $params = []): array { - $params = \array_filter($params, fn($param) => !\is_array($param)); + // $params = \array_filter($params, fn($param) => !\is_array($param)); $paramKeys = \array_keys($params); $paramValues = \array_values($params); diff --git a/src/Appwrite/Event/Validator/Event.php b/src/Appwrite/Event/Validator/Event.php index ce0f3e0067..b68cdfaa06 100644 --- a/src/Appwrite/Event/Validator/Event.php +++ b/src/Appwrite/Event/Validator/Event.php @@ -73,11 +73,11 @@ class Event extends Validator return false; } - if ($subtype ?? false) { + if ($subType) { if ($action && !\array_key_exists($action, $events[$type][$subType])) { return false; } - if (!($subResource ?? false) || !\array_key_exists($subType, $events[$type])) { + if (!($subResource) || !\array_key_exists($subType, $events[$type])) { return false; } } else { @@ -86,8 +86,8 @@ class Event extends Validator } } - if ($attribute ?? false) { - if (($subType ?? false)) { + if ($attribute) { + if (($subType)) { if (!\array_key_exists($attribute, $events[$type][$subType][$action])) { return false; } From da66733829a01156cce104ce15fbc5603ae1a33c Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Tue, 10 May 2022 15:25:49 +0200 Subject: [PATCH 31/35] tests: fix webhook tests --- src/Appwrite/Messaging/Adapter/Realtime.php | 1 + tests/e2e/Services/Webhooks/WebhooksBase.php | 256 +++++++++--------- .../Webhooks/WebhooksCustomClientTest.php | 232 ++++++++-------- .../Webhooks/WebhooksCustomServerTest.php | 212 +++++++-------- 4 files changed, 351 insertions(+), 350 deletions(-) diff --git a/src/Appwrite/Messaging/Adapter/Realtime.php b/src/Appwrite/Messaging/Adapter/Realtime.php index 10667f4765..a73ab7040b 100644 --- a/src/Appwrite/Messaging/Adapter/Realtime.php +++ b/src/Appwrite/Messaging/Adapter/Realtime.php @@ -246,6 +246,7 @@ class Realtime extends Adapter $roles = []; $permissionsChanged = false; $projectId = null; + // TODO: add method here to remove all the magic index accesses $parts = explode('.', $event); switch ($parts[0]) { diff --git a/tests/e2e/Services/Webhooks/WebhooksBase.php b/tests/e2e/Services/Webhooks/WebhooksBase.php index 815f27d45f..fd65b9484c 100644 --- a/tests/e2e/Services/Webhooks/WebhooksBase.php +++ b/tests/e2e/Services/Webhooks/WebhooksBase.php @@ -34,10 +34,10 @@ trait WebhooksBase $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('collections.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('collections.*.create', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$actorsId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$actorsId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('collections.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('collections.*.create', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$actorsId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$actorsId}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -106,16 +106,16 @@ trait WebhooksBase $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('collections.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('collections.*.attributes.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('collections.*.attributes.*.create', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.*.attributes.{$actorsId}_{$attributeId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.*.attributes.{$actorsId}_{$attributeId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$actorsId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$actorsId}.attributes.*", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$actorsId}.attributes.*.create", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$actorsId}.attributes.{$actorsId}_{$attributeId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$actorsId}.attributes.{$actorsId}_{$attributeId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('collections.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('collections.*.attributes.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('collections.*.attributes.*.create', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.*.attributes.{$actorsId}_{$attributeId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.*.attributes.{$actorsId}_{$attributeId}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$actorsId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$actorsId}.attributes.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$actorsId}.attributes.*.create", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$actorsId}.attributes.{$actorsId}_{$attributeId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$actorsId}.attributes.{$actorsId}_{$attributeId}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -135,16 +135,16 @@ trait WebhooksBase // $this->assertEquals($webhook['method'], 'DELETE'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('collections.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('collections.*.attributes.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('collections.*.attributes.*.delete', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.*.attributes.{$actorsId}_{$attributeId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.*.attributes.{$actorsId}_{$attributeId}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$actorsId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$actorsId}.attributes.*", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$actorsId}.attributes.*.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$actorsId}.attributes.{$actorsId}_{$attributeId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$actorsId}.attributes.{$actorsId}_{$attributeId}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('collections.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('collections.*.attributes.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('collections.*.attributes.*.delete', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.*.attributes.{$actorsId}_{$attributeId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.*.attributes.{$actorsId}_{$attributeId}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$actorsId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$actorsId}.attributes.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$actorsId}.attributes.*.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$actorsId}.attributes.{$actorsId}_{$attributeId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$actorsId}.attributes.{$actorsId}_{$attributeId}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -187,16 +187,16 @@ trait WebhooksBase $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('collections.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('collections.*.documents.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('collections.*.documents.*.create', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.*.documents.{$documentId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.*.documents.{$documentId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$actorsId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$actorsId}.documents.*", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$actorsId}.documents.*.create", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$actorsId}.documents.{$documentId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$actorsId}.documents.{$documentId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('collections.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('collections.*.documents.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('collections.*.documents.*.create', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.*.documents.{$documentId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.*.documents.{$documentId}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$actorsId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$actorsId}.documents.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$actorsId}.documents.*.create", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$actorsId}.documents.{$documentId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$actorsId}.documents.{$documentId}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -246,16 +246,16 @@ trait WebhooksBase $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('collections.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('collections.*.documents.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('collections.*.documents.*.update', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.*.documents.{$documentId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.*.documents.{$documentId}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$actorsId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$actorsId}.documents.*", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$actorsId}.documents.*.update", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$actorsId}.documents.{$documentId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$actorsId}.documents.{$documentId}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('collections.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('collections.*.documents.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('collections.*.documents.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.*.documents.{$documentId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.*.documents.{$documentId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$actorsId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$actorsId}.documents.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$actorsId}.documents.*.update", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$actorsId}.documents.{$documentId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$actorsId}.documents.{$documentId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -312,16 +312,16 @@ trait WebhooksBase $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('collections.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('collections.*.documents.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('collections.*.documents.*.delete', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.*.documents.{$documentId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.*.documents.{$documentId}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$actorsId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$actorsId}.documents.*", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$actorsId}.documents.*.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$actorsId}.documents.{$documentId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$actorsId}.documents.{$documentId}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('collections.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('collections.*.documents.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('collections.*.documents.*.delete', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.*.documents.{$documentId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.*.documents.{$documentId}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$actorsId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$actorsId}.documents.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$actorsId}.documents.*.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$actorsId}.documents.{$documentId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$actorsId}.documents.{$documentId}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -365,10 +365,10 @@ trait WebhooksBase $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('buckets.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('buckets.*.create', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("buckets.{$bucketId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("buckets.{$bucketId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('buckets.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('buckets.*.create', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("buckets.{$bucketId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("buckets.{$bucketId}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -410,10 +410,10 @@ trait WebhooksBase $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('buckets.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('buckets.*.update', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("buckets.{$bucketId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("buckets.{$bucketId}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('buckets.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('buckets.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("buckets.{$bucketId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("buckets.{$bucketId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -470,16 +470,16 @@ trait WebhooksBase $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('buckets.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('buckets.*.files.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('buckets.*.files.*.create', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("buckets.*.files.{$fileId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("buckets.*.files.{$fileId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("buckets.{$bucketId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("buckets.{$bucketId}.files.*", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("buckets.{$bucketId}.files.*.create", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("buckets.{$bucketId}.files.{$fileId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("buckets.{$bucketId}.files.{$fileId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('buckets.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('buckets.*.files.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('buckets.*.files.*.create', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("buckets.*.files.{$fileId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("buckets.*.files.{$fileId}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("buckets.{$bucketId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("buckets.{$bucketId}.files.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("buckets.{$bucketId}.files.*.create", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("buckets.{$bucketId}.files.{$fileId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("buckets.{$bucketId}.files.{$fileId}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -525,16 +525,16 @@ trait WebhooksBase $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('buckets.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('buckets.*.files.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('buckets.*.files.*.update', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("buckets.*.files.{$fileId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("buckets.*.files.{$fileId}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("buckets.{$bucketId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("buckets.{$bucketId}.files.*", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("buckets.{$bucketId}.files.*.update", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("buckets.{$bucketId}.files.{$fileId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("buckets.{$bucketId}.files.{$fileId}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('buckets.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('buckets.*.files.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('buckets.*.files.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("buckets.*.files.{$fileId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("buckets.*.files.{$fileId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("buckets.{$bucketId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("buckets.{$bucketId}.files.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("buckets.{$bucketId}.files.*.update", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("buckets.{$bucketId}.files.{$fileId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("buckets.{$bucketId}.files.{$fileId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -575,16 +575,16 @@ trait WebhooksBase $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('buckets.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('buckets.*.files.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('buckets.*.files.*.delete', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("buckets.*.files.{$fileId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("buckets.*.files.{$fileId}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("buckets.{$bucketId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("buckets.{$bucketId}.files.*", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("buckets.{$bucketId}.files.*.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("buckets.{$bucketId}.files.{$fileId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("buckets.{$bucketId}.files.{$fileId}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('buckets.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('buckets.*.files.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('buckets.*.files.*.delete', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("buckets.*.files.{$fileId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("buckets.*.files.{$fileId}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("buckets.{$bucketId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("buckets.{$bucketId}.files.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("buckets.{$bucketId}.files.*.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("buckets.{$bucketId}.files.{$fileId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("buckets.{$bucketId}.files.{$fileId}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -624,10 +624,10 @@ trait WebhooksBase $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('buckets.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('buckets.*.delete', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("buckets.{$bucketId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("buckets.{$bucketId}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('buckets.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('buckets.*.delete', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("buckets.{$bucketId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("buckets.{$bucketId}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -662,10 +662,10 @@ trait WebhooksBase $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('teams.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('teams.*.create', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("teams.{$teamId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("teams.{$teamId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('teams.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('teams.*.create', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("teams.{$teamId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("teams.{$teamId}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -706,10 +706,10 @@ trait WebhooksBase $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('teams.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('teams.*.update', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("teams.{$teamId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("teams.{$teamId}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('teams.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('teams.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("teams.{$teamId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("teams.{$teamId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -754,10 +754,10 @@ trait WebhooksBase $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('teams.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('teams.*.delete', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("teams.{$teamId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("teams.{$teamId}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('teams.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('teams.*.delete', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("teams.{$teamId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("teams.{$teamId}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -808,16 +808,16 @@ trait WebhooksBase $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('teams.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('teams.*.memberships.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('teams.*.memberships.*.create', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("teams.*.memberships.{$membershipId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("teams.*.memberships.{$membershipId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("teams.{$teamId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("teams.{$teamId}.memberships.*", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("teams.{$teamId}.memberships.*.create", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("teams.{$teamId}.memberships.{$membershipId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("teams.{$teamId}.memberships.{$membershipId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('teams.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('teams.*.memberships.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('teams.*.memberships.*.create', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("teams.*.memberships.{$membershipId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("teams.*.memberships.{$membershipId}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("teams.{$teamId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("teams.{$teamId}.memberships.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("teams.{$teamId}.memberships.*.create", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("teams.{$teamId}.memberships.{$membershipId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("teams.{$teamId}.memberships.{$membershipId}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -879,16 +879,16 @@ trait WebhooksBase $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('teams.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('teams.*.memberships.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('teams.*.memberships.*.delete', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("teams.*.memberships.{$membershipId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("teams.*.memberships.{$membershipId}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("teams.{$teamId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("teams.{$teamId}.memberships.*", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("teams.{$teamId}.memberships.*.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("teams.{$teamId}.memberships.{$membershipId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("teams.{$teamId}.memberships.{$membershipId}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('teams.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('teams.*.memberships.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('teams.*.memberships.*.delete', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("teams.*.memberships.{$membershipId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("teams.*.memberships.{$membershipId}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("teams.{$teamId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("teams.{$teamId}.memberships.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("teams.{$teamId}.memberships.*.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("teams.{$teamId}.memberships.{$membershipId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("teams.{$teamId}.memberships.{$membershipId}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); diff --git a/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php b/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php index 5789f7ef8c..47eebe4b75 100644 --- a/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php +++ b/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php @@ -43,10 +43,10 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('users.*.create', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event'],); - $this->assertStringContainsString("users.{$id}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('users.*.create', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events'],); + $this->assertStringContainsString("users.{$id}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -116,10 +116,10 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('users.*.delete', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('users.*.delete', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -166,16 +166,16 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('users.*.sessions.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('users.*.sessions.*.create', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.*.sessions.{$sessionId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.*.sessions.{$sessionId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.sessions.*", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.sessions.*.create", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.sessions.{$sessionId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.sessions.{$sessionId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('users.*.sessions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('users.*.sessions.*.create', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.*.sessions.{$sessionId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.*.sessions.{$sessionId}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.sessions.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.sessions.*.create", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.sessions.{$sessionId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.sessions.{$sessionId}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -251,16 +251,16 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('users.*.sessions.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('users.*.sessions.*.delete', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.*.sessions.{$sessionId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.*.sessions.{$sessionId}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.sessions.*", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.sessions.*.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.sessions.{$sessionId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.sessions.{$sessionId}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('users.*.sessions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('users.*.sessions.*.delete', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.*.sessions.{$sessionId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.*.sessions.{$sessionId}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.sessions.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.sessions.*.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.sessions.{$sessionId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.sessions.{$sessionId}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -333,16 +333,16 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('users.*.sessions.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('users.*.sessions.*.delete', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.*.sessions.{$sessionId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.*.sessions.{$sessionId}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.sessions.*", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.sessions.*.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.sessions.{$sessionId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.sessions.{$sessionId}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('users.*.sessions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('users.*.sessions.*.delete', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.*.sessions.{$sessionId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.*.sessions.{$sessionId}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.sessions.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.sessions.*.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.sessions.{$sessionId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.sessions.{$sessionId}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -420,12 +420,12 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('users.*.update', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('users.*.update.name', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.update.name", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('users.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('users.*.update.name', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.update.name", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -469,12 +469,12 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('users.*.update', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('users.*.update.password', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.update.password", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('users.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('users.*.update.password', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.update.password", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -520,12 +520,12 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('users.*.update', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('users.*.update.email', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.update.email", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('users.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('users.*.update.email', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.update.email", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -572,12 +572,12 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('users.*.update', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('users.*.update.prefs', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.update.prefs", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('users.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('users.*.update.prefs', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.update.prefs", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -623,16 +623,16 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('users.*.recovery.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('users.*.recovery.*.create', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.*.recovery.{$recoveryId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.*.recovery.{$recoveryId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.recovery.*", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.recovery.*.create", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.recovery.{$recoveryId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.recovery.{$recoveryId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('users.*.recovery.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('users.*.recovery.*.create', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.*.recovery.{$recoveryId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.*.recovery.{$recoveryId}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.recovery.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.recovery.*.create", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.recovery.{$recoveryId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.recovery.{$recoveryId}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -679,16 +679,16 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('users.*.recovery.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('users.*.recovery.*.update', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.*.recovery.{$recoveryId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.*.recovery.{$recoveryId}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.recovery.*", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.recovery.*.update", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.recovery.{$recoveryId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.recovery.{$recoveryId}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('users.*.recovery.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('users.*.recovery.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.*.recovery.{$recoveryId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.*.recovery.{$recoveryId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.recovery.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.recovery.*.update", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.recovery.{$recoveryId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.recovery.{$recoveryId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -731,16 +731,16 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('users.*.verification.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('users.*.verification.*.create', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.*.verification.{$verificationId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.*.verification.{$verificationId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.verification.*", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.verification.*.create", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.verification.{$verificationId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.verification.{$verificationId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('users.*.verification.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('users.*.verification.*.create', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.*.verification.{$verificationId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.*.verification.{$verificationId}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.verification.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.verification.*.create", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.verification.{$verificationId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.verification.{$verificationId}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -785,16 +785,16 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('users.*.verification.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('users.*.verification.*.update', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.*.verification.{$verificationId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.*.verification.{$verificationId}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.verification.*", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.verification.*.update", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.verification.{$verificationId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.verification.{$verificationId}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('users.*.verification.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('users.*.verification.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.*.verification.{$verificationId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.*.verification.{$verificationId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.verification.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.verification.*.update", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.verification.{$verificationId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.verification.{$verificationId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -839,20 +839,20 @@ class WebhooksCustomClientTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('teams.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('teams.*.memberships.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('teams.*.memberships.*.update', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('teams.*.memberships.*.update.status', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("teams.*.memberships.{$membershipUid}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("teams.*.memberships.{$membershipUid}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("teams.*.memberships.{$membershipUid}.update.status", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("teams.{$teamUid}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("teams.{$teamUid}.memberships.*", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("teams.{$teamUid}.memberships.*.update", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("teams.{$teamUid}.memberships.*.update.status", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("teams.{$teamUid}.memberships.{$membershipUid}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("teams.{$teamUid}.memberships.{$membershipUid}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("teams.{$teamUid}.memberships.{$membershipUid}.update.status", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('teams.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('teams.*.memberships.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('teams.*.memberships.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('teams.*.memberships.*.update.status', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("teams.*.memberships.{$membershipUid}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("teams.*.memberships.{$membershipUid}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("teams.*.memberships.{$membershipUid}.update.status", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("teams.{$teamUid}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("teams.{$teamUid}.memberships.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("teams.{$teamUid}.memberships.*.update", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("teams.{$teamUid}.memberships.*.update.status", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("teams.{$teamUid}.memberships.{$membershipUid}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("teams.{$teamUid}.memberships.{$membershipUid}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("teams.{$teamUid}.memberships.{$membershipUid}.update.status", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); diff --git a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php index bb8c35a1ec..7c815595fe 100644 --- a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php +++ b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php @@ -42,10 +42,10 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('collections.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('collections.*.update', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$id}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('collections.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('collections.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$id}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -90,16 +90,16 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('collections.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('collections.*.indexes.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('collections.*.indexes.*.create', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.*.indexes.{$actorsId}_{$indexKey}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.*.indexes.{$actorsId}_{$indexKey}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$actorsId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$actorsId}.indexes.*", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$actorsId}.indexes.*.create", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$actorsId}.indexes.{$actorsId}_{$indexKey}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$actorsId}.indexes.{$actorsId}_{$indexKey}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('collections.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('collections.*.indexes.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('collections.*.indexes.*.create', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.*.indexes.{$actorsId}_{$indexKey}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.*.indexes.{$actorsId}_{$indexKey}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$actorsId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$actorsId}.indexes.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$actorsId}.indexes.*.create", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$actorsId}.indexes.{$actorsId}_{$indexKey}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$actorsId}.indexes.{$actorsId}_{$indexKey}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -118,16 +118,16 @@ class WebhooksCustomServerTest extends Scope // $this->assertEquals($webhook['method'], 'DELETE'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('collections.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('collections.*.indexes.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('collections.*.indexes.*.delete', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.*.indexes.{$actorsId}_{$indexKey}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.*.indexes.{$actorsId}_{$indexKey}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$actorsId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$actorsId}.indexes.*", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$actorsId}.indexes.*.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$actorsId}.indexes.{$actorsId}_{$indexKey}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$actorsId}.indexes.{$actorsId}_{$indexKey}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('collections.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('collections.*.indexes.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('collections.*.indexes.*.delete', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.*.indexes.{$actorsId}_{$indexKey}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.*.indexes.{$actorsId}_{$indexKey}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$actorsId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$actorsId}.indexes.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$actorsId}.indexes.*.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$actorsId}.indexes.{$actorsId}_{$indexKey}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$actorsId}.indexes.{$actorsId}_{$indexKey}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -171,10 +171,10 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('collections.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('collections.*.delete', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("collections.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event'],); - $this->assertStringContainsString("collections.{$id}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('collections.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('collections.*.delete', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("collections.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events'],); + $this->assertStringContainsString("collections.{$id}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -218,10 +218,10 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('users.*.create', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event'],); - $this->assertStringContainsString("users.{$id}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('users.*.create', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events'],); + $this->assertStringContainsString("users.{$id}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -265,12 +265,12 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('users.*.update', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('users.*.update.prefs', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.update.prefs", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('users.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('users.*.update.prefs', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.update.prefs", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -305,12 +305,12 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('users.*.update', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('users.*.update.status', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.update.status", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('users.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('users.*.update.status', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.update.status", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -348,10 +348,10 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('users.*.delete', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("users.{$id}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('users.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('users.*.delete', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("users.{$id}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -393,10 +393,10 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('functions.*.create', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.{$id}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('functions.*.create', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.{$id}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -441,10 +441,10 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('functions.*.update', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.{$id}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('functions.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.{$id}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -485,12 +485,12 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('functions.*.deployments.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.*.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.{$id}.deployments.*", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('functions.*.deployments.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.*.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.{$id}.deployments.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -527,16 +527,16 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('functions.*.deployments.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('functions.*.deployments.*.update', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.*.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.*.deployments.{$deploymentId}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.{$id}.deployments.*", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.{$id}.deployments.*.update", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('functions.*.deployments.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('functions.*.deployments.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.*.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.*.deployments.{$deploymentId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.{$id}.deployments.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.{$id}.deployments.*.update", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -573,16 +573,16 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('functions.*.executions.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('functions.*.executions.*.create', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.*.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.*.executions.{$executionId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.{$id}.executions.*", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.{$id}.executions.*.create", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.{$id}.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.{$id}.executions.{$executionId}.create", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('functions.*.executions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('functions.*.executions.*.create', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.*.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.*.executions.{$executionId}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.{$id}.executions.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.{$id}.executions.*.create", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.{$id}.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.{$id}.executions.{$executionId}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -595,16 +595,16 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('functions.*.executions.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('functions.*.executions.*.update', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.*.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.*.executions.{$executionId}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.{$id}.executions.*", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.{$id}.executions.*.update", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.{$id}.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.{$id}.executions.{$executionId}.update", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('functions.*.executions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('functions.*.executions.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.*.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.*.executions.{$executionId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.{$id}.executions.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.{$id}.executions.*.update", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.{$id}.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.{$id}.executions.{$executionId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -639,16 +639,16 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('functions.*.deployments.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('functions.*.deployments.*.delete', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.*.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.*.deployments.{$deploymentId}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.{$id}.deployments.*", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.{$id}.deployments.*.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('functions.*.deployments.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('functions.*.deployments.*.delete', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.*.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.*.deployments.{$deploymentId}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.{$id}.deployments.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.{$id}.deployments.*.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -683,10 +683,10 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString('functions.*.delete', $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Event']); - $this->assertStringContainsString("functions.{$id}.delete", $webhook['headers']['X-Appwrite-Webhook-Event']); + $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString('functions.*.delete', $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); + $this->assertStringContainsString("functions.{$id}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); From 88732e5fdb4ec27671a14982040bf0e6d825f5a7 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Tue, 10 May 2022 15:32:16 +0200 Subject: [PATCH 32/35] style: fix whitespace --- src/Appwrite/Messaging/Adapter/Realtime.php | 62 ++++++++++----------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/src/Appwrite/Messaging/Adapter/Realtime.php b/src/Appwrite/Messaging/Adapter/Realtime.php index a73ab7040b..ad38444cbe 100644 --- a/src/Appwrite/Messaging/Adapter/Realtime.php +++ b/src/Appwrite/Messaging/Adapter/Realtime.php @@ -11,7 +11,7 @@ class Realtime extends Adapter /** * Connection Tree * - * [CONNECTION_ID] -> + * [CONNECTION_ID] -> * 'projectId' -> [PROJECT_ID] * 'roles' -> [ROLE_x, ROLE_Y] * 'channels' -> [CHANNEL_NAME_X, CHANNEL_NAME_Y, CHANNEL_NAME_Z] @@ -20,13 +20,13 @@ class Realtime extends Adapter /** * Subscription Tree - * - * [PROJECT_ID] -> - * [ROLE_X] -> + * + * [PROJECT_ID] -> + * [ROLE_X] -> * [CHANNEL_NAME_X] -> [CONNECTION_ID] * [CHANNEL_NAME_Y] -> [CONNECTION_ID] * [CHANNEL_NAME_Z] -> [CONNECTION_ID] - * [ROLE_Y] -> + * [ROLE_Y] -> * [CHANNEL_NAME_X] -> [CONNECTION_ID] * [CHANNEL_NAME_Y] -> [CONNECTION_ID] * [CHANNEL_NAME_Z] -> [CONNECTION_ID] @@ -35,12 +35,12 @@ class Realtime extends Adapter /** * Adds a subscription. - * - * @param string $projectId - * @param mixed $identifier - * @param array $roles - * @param array $channels - * @return void + * + * @param string $projectId + * @param mixed $identifier + * @param array $roles + * @param array $channels + * @return void */ public function subscribe(string $projectId, mixed $identifier, array $roles, array $channels): void { @@ -69,7 +69,7 @@ class Realtime extends Adapter * Removes Subscription. * * @param mixed $connection - * @return void + * @return void */ public function unsubscribe(mixed $connection): void { @@ -99,9 +99,9 @@ class Realtime extends Adapter /** * Checks if Channel has a subscriber. - * @param string $projectId - * @param string $role - * @param string $channel + * @param string $projectId + * @param string $role + * @param string $channel * @return bool */ public function hasSubscriber(string $projectId, string $role, string $channel = ''): bool @@ -118,14 +118,14 @@ class Realtime extends Adapter } /** - * Sends an event to the Realtime Server. - * @param string $projectId - * @param array $payload - * @param string $event - * @param array $channels - * @param array $roles - * @param array $options - * @return void + * Sends an event to the Realtime Server + * @param string $projectId + * @param array $payload + * @param string $event + * @param array $channels + * @param array $roles + * @param array $options + * @return void */ public static function send(string $projectId, array $payload, array $events, array $channels, array $roles, array $options = []): void { @@ -152,7 +152,7 @@ class Realtime extends Adapter /** * Identifies the receivers of all subscriptions, based on the permissions and event. - * + * * Example of performance with an event with user:XXX permissions and with X users spread across 10 different channels: * - 0.014 ms (±6.88%) | 10 Connections / 100 Subscriptions * - 0.070 ms (±3.71%) | 100 Connections / 1,000 Subscriptions @@ -160,7 +160,7 @@ class Realtime extends Adapter * - 10.866 ms (±1.01%) | 10,000 Connections / 100,000 Subscriptions * - 110.201 ms (±2.32%) | 100,000 Connections / 1,000,000 Subscriptions * - 1,121.328 ms (±0.84%) | 1,000,000 Connections / 10,000,000 Subscriptions - * + * * @param array $event */ public function getSubscribers(array $event) @@ -205,10 +205,10 @@ class Realtime extends Adapter } /** - * Converts the channels from the Query Params into an array. + * Converts the channels from the Query Params into an array. * Also renames the account channel to account.USER_ID and removes all illegal account channel variations. - * @param array $channels - * @param string $userId + * @param array $channels + * @param string $userId * @return array */ public static function convertChannels(array $channels, string $userId): array @@ -235,9 +235,9 @@ class Realtime extends Adapter /** * Create channels array based on the event name and payload. * - * @param string $event - * @param Document $payload - * @param Document|null $project + * @param string $event + * @param Document $payload + * @param Document|null $project * @return array */ public static function fromPayload(string $event, Document $payload, Document $project = null, Document $collection = null, Document $bucket = null): array From 0525d2cc7bae23da6a448a5952b2defb39d8304c Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Tue, 10 May 2022 15:52:55 +0200 Subject: [PATCH 33/35] fix: remove eventData --- app/controllers/api/account.php | 6 ++++-- app/controllers/api/users.php | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index aae2cd9176..f81c0faf6a 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -724,10 +724,12 @@ App::post('/v1/account/sessions/magic-url') ->trigger() ; - $events->setParam('eventData', $response->output( + $events->setPayload( + $response->output( $token->setAttribute('secret', $loginSecret), Response::MODEL_TOKEN - )); + ) + ); // Hide secret for clients $token->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? $loginSecret : ''); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index ad1d018ece..6e96f72642 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -715,8 +715,8 @@ App::delete('/v1/users/:userId/sessions') $dbForProject->deleteCachedDocument('users', $user->getId()); $events - ->setParam('eventData', $response->output($user, Response::MODEL_USER)) ->setParam('userId', $user->getId()) + ->setPayload($response->output($user, Response::MODEL_USER)) ; $usage @@ -782,8 +782,8 @@ App::delete('/v1/users/:userId') ; $events - ->setParam('eventData', $response->output($clone, Response::MODEL_USER)) ->setParam('userId', $user->getId()) + ->setPayload($response->output($clone, Response::MODEL_USER)) ; $usage From 4b632b13ea7592eadb827eb3efca5e75477b5217 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Wed, 11 May 2022 19:05:43 +0200 Subject: [PATCH 34/35] fix: merge sync leftover --- app/workers/certificates.php | 1 + src/Appwrite/Event/Certificate.php | 40 ++++++------------------------ 2 files changed, 9 insertions(+), 32 deletions(-) diff --git a/app/workers/certificates.php b/app/workers/certificates.php index 1c2b590969..28a893855c 100644 --- a/app/workers/certificates.php +++ b/app/workers/certificates.php @@ -5,6 +5,7 @@ use Appwrite\Network\Validator\CNAME; use Appwrite\Resque\Worker; use Utopia\App; use Utopia\CLI\Console; +use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; diff --git a/src/Appwrite/Event/Certificate.php b/src/Appwrite/Event/Certificate.php index 59c2ce9a76..894c094961 100644 --- a/src/Appwrite/Event/Certificate.php +++ b/src/Appwrite/Event/Certificate.php @@ -7,8 +7,7 @@ use Utopia\Database\Document; class Certificate extends Event { - protected bool $validateTarget = false; - protected bool $validateCNAME = false; + protected bool $skipRenewCheck = false; protected ?Document $domain = null; public function __construct() @@ -40,49 +39,26 @@ class Certificate extends Event } /** - * Set if the target needs be validated. + * Set if the certificate needs to be validated. * - * @param bool $validateTarget + * @param bool $skipRenewCheck * @return self */ - public function setValidateTarget(bool $validateTarget): self + public function setSkipRenewCheck(bool $skipRenewCheck): self { - $this->validateTarget = $validateTarget; + $this->skipRenewCheck = $skipRenewCheck; return $this; } /** - * Return if the domain target will be validated. + * Return if the certificate needs be validated. * * @return bool */ - public function getValidateTarget(): bool + public function getSkipRenewCheck(): bool { - return $this->validateTarget; - } - - /** - * Set if the CNAME needs to be validated. - * - * @param bool $validateCNAME - * @return self - */ - public function setValidateCNAME(bool $validateCNAME): self - { - $this->validateCNAME = $validateCNAME; - - return $this; - } - - /** - * Return if the CNAME will be validated. - * - * @return bool - */ - public function getValidateCNAME(): bool - { - return $this->validateCNAME; + return $this->skipRenewCheck; } /** From 03723c0f90571f37db841b66a50a654fd9a3fddc Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Thu, 12 May 2022 19:24:54 +0200 Subject: [PATCH 35/35] tests: remove unused event --- src/Appwrite/Migration/Version/V13.php | 1 - tests/unit/Migration/MigrationV13Test.php | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Appwrite/Migration/Version/V13.php b/src/Appwrite/Migration/Version/V13.php index 8255fa9d27..4f1003af5d 100644 --- a/src/Appwrite/Migration/Version/V13.php +++ b/src/Appwrite/Migration/Version/V13.php @@ -22,7 +22,6 @@ class V13 extends Migration 'account.delete', 'account.sessions.create', 'account.sessions.delete', - 'account.sessions.update', 'database.collections.create', 'database.collections.update', 'database.collections.delete', diff --git a/tests/unit/Migration/MigrationV13Test.php b/tests/unit/Migration/MigrationV13Test.php index 2315a4ff63..3d0b0e2971 100644 --- a/tests/unit/Migration/MigrationV13Test.php +++ b/tests/unit/Migration/MigrationV13Test.php @@ -46,6 +46,6 @@ class MigrationV13Test extends MigrationTest foreach ($events as $event) { $this->assertTrue((new Event())->isValid($event), $event); } - $this->assertCount(45, $events); + $this->assertCount(44, $events); } }