diff --git a/README-CN.md b/README-CN.md index cc8457fd5..826163ee2 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 c74b33cc2..dea890c3a 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/config/events.php b/app/config/events.php index bfb70e218..2c2ded49d 100644 --- a/app/config/events.php +++ b/app/config/events.php @@ -7,279 +7,217 @@ 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' => '', - ], - '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', + '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.', + ] ], + '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/app/controllers/api/account.php b/app/controllers/api/account.php index eedc8f6df..aaaf5eaa4 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\Event\Audit $audits */ /** @var Appwrite\Stats\Stats $usage */ + /** @var Appwrite\Event\Event $events */ $email = \strtolower($email); if ('console' === $project->getId()) { @@ -119,14 +120,13 @@ App::post('/v1/account') Authorization::setRole('role:' . Auth::USER_ROLE_MEMBER); $audits - ->setParam('userId', $user->getId()) - ->setParam('event', 'account.create') - ->setParam('resource', 'user/' . $user->getId()) + ->setResource('user/'.$user->getId()) + ->setUser($user) ; - $usage - ->setParam('users.create', 1) - ; + $usage->setParam('users.create', 1); + $events->setParam('userId', $user->getId()); + $response->setStatusCode(Response::STATUS_CODE_CREATED); $response->dynamic($user, Response::MODEL_USER); }); @@ -134,7 +134,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,14 +155,16 @@ 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 */ /** @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 */ $email = \strtolower($email); $protocol = $request->getProtocol(); @@ -173,12 +175,6 @@ App::post('/v1/account/sessions') ); 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 } @@ -214,11 +210,8 @@ App::post('/v1/account/sessions') $dbForProject->deleteCachedDocument('users', $profile->getId()); $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', '')) + ->setResource('user/'.$profile->getId()) + ->setUser($profile) ; if (!Config::getParam('domainVerification')) { @@ -245,6 +238,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); }); @@ -367,7 +366,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}') @@ -391,7 +390,8 @@ 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 */ $protocol = $request->getProtocol(); @@ -572,23 +572,24 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $dbForProject->deleteCachedDocument('users', $user->getId()); $audits - ->setParam('userId', $user->getId()) - ->setParam('event', 'account.sessions.create') - ->setParam('resource', 'user/' . $user->getId()) - ->setParam('data', ['provider' => $provider]) + ->setResource('user/'.$user->getId()) + ->setUser($user) ; - $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)])) - ; + $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 @@ -644,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); @@ -691,9 +692,6 @@ App::post('/v1/account/sessions/magic-url') 'search' => implode(' ', [$userId, $email]), 'deleted' => false ]))); - - $mails->setParam('event', 'users.create'); - $audits->setParam('event', 'users.create'); } $loginSecret = Auth::tokenGenerator(); @@ -728,29 +726,26 @@ 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) + ->setType(MAIL_TYPE_MAGIC_SESSION) + ->setRecipient($user->getAttribute('email')) + ->setUrl($url) + ->setLocale($locale->default) ->trigger() ; - $events - ->setParam('eventData', - $response->output($token->setAttribute('secret', $loginSecret), + $events->setPayload( + $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 - ->setParam('userId', $user->getId()) - ->setParam('resource', 'users/'.$user->getId()) + ->setResource('user/'.$user->getId()) + ->setUser($user) ; $response @@ -763,7 +758,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') @@ -781,7 +776,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 */ @@ -789,7 +785,8 @@ 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 = Authorization::skip(fn() => $dbForProject->getDocument('users', $userId)); @@ -841,8 +838,7 @@ App::put('/v1/account/sessions/magic-url') $dbForProject->deleteDocument('tokens', $token); $dbForProject->deleteCachedDocument('users', $user->getId()); - $user - ->setAttribute('emailVerification', true); + $user->setAttribute('emailVerification', true); $user = $dbForProject->updateDocument('users', $user->getId(), $user); @@ -850,16 +846,15 @@ App::put('/v1/account/sessions/magic-url') throw new Exception('Failed saving user to DB', 500, Exception::GENERAL_SERVER_ERROR); } - $audits + $audits->setResource('user/'.$user->getId()); + + $events ->setParam('userId', $user->getId()) - ->setParam('event', 'account.sessions.create') - ->setParam('resource', 'users/'.$user->getId()) + ->setParam('sessionId', $session->getId()) ; 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(); @@ -883,7 +878,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 +899,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 */ @@ -912,8 +908,9 @@ 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 */ $protocol = $request->getProtocol(); @@ -989,21 +986,20 @@ App::post('/v1/account/sessions/anonymous') $dbForProject->deleteCachedDocument('users', $user->getId()); - $audits - ->setParam('userId', $user->getId()) - ->setParam('event', 'account.sessions.create') - ->setParam('resource', 'user/' . $user->getId()) - ; + $audits->setResource('user/'.$user->getId()); $usage ->setParam('users.sessions.create', 1) ->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)])) - ; + $response->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)])); } $response @@ -1090,9 +1086,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); }); @@ -1117,9 +1112,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); }); @@ -1156,9 +1150,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), @@ -1194,26 +1187,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 = []; @@ -1242,12 +1217,10 @@ App::get('/v1/account/logs') } - $usage - ->setParam('users.read', 1) - ; + $usage->setParam('users.read', 1); $response->dynamic(new Document([ - 'total' => $audit->countLogsByUserAndEvents($user->getId(), $auditEvents), + 'total' => $audit->countLogsByUser($user->getId()), 'logs' => $output, ]), Response::MODEL_LOG_LIST); }); @@ -1283,7 +1256,6 @@ App::get('/v1/account/sessions/:sessionId') foreach ($sessions as $session) {/** @var Document $session */ if ($sessionId == $session->getId()) { - $countryName = $locale->getText('countries.'.strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown')); $session @@ -1291,9 +1263,7 @@ App::get('/v1/account/sessions/:sessionId') ->setAttribute('countryName', $countryName) ; - $usage - ->setParam('users.read', 1) - ; + $usage->setParam('users.read', 1); return $response->dynamic($session, Response::MODEL_SESSION); } @@ -1305,7 +1275,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') @@ -1320,12 +1290,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\Event\Audit $audits */ /** @var Appwrite\Stats\Stats $usage */ + /** @var Appwrite\Stats\Stats $events */ $user = $dbForProject->updateDocument('users', $user->getId(), $user ->setAttribute('name', $name) @@ -1333,14 +1305,12 @@ App::patch('/v1/account/name') ); $audits - ->setParam('userId', $user->getId()) - ->setParam('event', 'account.update.name') - ->setParam('resource', 'user/' . $user->getId()) + ->setResource('user/'.$user->getId()) + ->setUser($user) ; - $usage - ->setParam('users.update', 1) - ; + $usage->setParam('users.update', 1); + $events->setParam('userId', $user->getId()); $response->dynamic($user, Response::MODEL_USER); }); @@ -1348,7 +1318,7 @@ App::patch('/v1/account/name') 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') @@ -1364,39 +1334,43 @@ 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\Event\Audit $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 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()) ); $audits - ->setParam('userId', $user->getId()) - ->setParam('event', 'account.update.password') - ->setParam('resource', 'user/' . $user->getId()) + ->setResource('user/'.$user->getId()) + ->setUser($user) ; - $usage - ->setParam('users.update', 1) - ; + $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') @@ -1412,12 +1386,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\Event\Audit $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 @@ -1447,21 +1423,20 @@ App::patch('/v1/account/email') } $audits - ->setParam('userId', $user->getId()) - ->setParam('event', 'account.update.email') - ->setParam('resource', 'user/' . $user->getId()) + ->setResource('user/'.$user->getId()) + ->setUser($user) ; - $usage - ->setParam('users.update', 1) - ; + $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') @@ -1476,30 +1451,28 @@ 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\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('event', 'account.update.prefs') - ->setParam('resource', 'user/' . $user->getId()) - ; + $audits->setResource('user/'.$user->getId()); + $usage->setParam('users.update', 1); + $events->setParam('userId', $user->getId()); - $usage - ->setParam('users.update', 1) - ; $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') @@ -1519,7 +1492,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 */ @@ -1530,32 +1503,28 @@ 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()) - ->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 - ->setParam('eventData', $response->output($user, Response::MODEL_USER)) + ->setParam('userId', $user->getId()) + ->setPayload($response->output($user, Response::MODEL_USER)) ; 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')) @@ -1567,7 +1536,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') @@ -1590,7 +1559,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 */ @@ -1607,11 +1576,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); @@ -1636,7 +1601,9 @@ App::delete('/v1/account/sessions/:sessionId') $dbForProject->deleteCachedDocument('users', $user->getId()); $events - ->setParam('eventData', $response->output($session, Response::MODEL_SESSION)) + ->setParam('userId', $user->getId()) + ->setParam('sessionId', $session->getId()) + ->setPayload($response->output($session, Response::MODEL_SESSION)) ; $usage @@ -1654,7 +1621,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') @@ -1681,7 +1648,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 */ @@ -1710,7 +1677,7 @@ App::patch('/v1/account/sessions/:sessionId') $appSecret = $project->getAttribute('authProviders', [])[$provider.'Secret'] ?? '{}'; $className = 'Appwrite\\Auth\\OAuth2\\'.\ucfirst($provider); - + if (!\class_exists($className)) { throw new Exception('Provider is not supported', 501, Exception::PROJECT_PROVIDER_UNSUPPORTED); } @@ -1729,14 +1696,12 @@ App::patch('/v1/account/sessions/:sessionId') $dbForProject->deleteCachedDocument('users', $user->getId()); - $audits - ->setParam('userId', $user->getId()) - ->setParam('event', 'account.sessions.update') - ->setParam('resource', 'user/' . $user->getId()) - ; + $audits->setResource('user/' . $user->getId()); $events - ->setParam('eventData', $response->output($session, Response::MODEL_SESSION)) + ->setParam('userId', $user->getId()) + ->setParam('sessionId', $session->getId()) + ->setPayload($response->output($session, Response::MODEL_SESSION)) ; $usage @@ -1755,7 +1720,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') @@ -1777,7 +1742,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 */ @@ -1787,11 +1752,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 @@ -1818,7 +1779,9 @@ App::delete('/v1/account/sessions') $numOfSessions = count($sessions); $events - ->setParam('eventData', $response->output(new Document([ + ->setParam('userId', $user->getId()) + ->setParam('sessionId', $session->getId()) + ->setPayload($response->output(new Document([ 'sessions' => $sessions, 'total' => $numOfSessions, ]), Response::MODEL_SESSION_LIST)) @@ -1828,6 +1791,7 @@ App::delete('/v1/account/sessions') ->setParam('users.sessions.delete', $numOfSessions) ->setParam('users.update', 1) ; + $response->noContent(); }); @@ -1835,7 +1799,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') @@ -1862,8 +1826,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 */ @@ -1917,37 +1881,30 @@ 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) + ->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 - )) - ; - - $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()) + ->setParam('tokenId', $recovery->getId()) + ->setUser($profile) + ->setPayload($response->output( + $recovery->setAttribute('secret', $secret), + Response::MODEL_TOKEN + )) ; - $usage - ->setParam('users.update', 1) - ; + // Hide secret for clients + $recovery->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? $secret : ''); + + $audits->setResource('user/' . $profile->getId()); + $usage->setParam('users.update', 1); + $response->setStatusCode(Response::STATUS_CODE_CREATED); $response->dynamic($recovery, Response::MODEL_TOKEN); }); @@ -1956,7 +1913,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') @@ -1974,11 +1931,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\Event\Audit $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); @@ -2014,15 +1973,15 @@ App::put('/v1/account/recovery') $dbForProject->deleteDocument('tokens', $recovery); $dbForProject->deleteCachedDocument('users', $profile->getId()); - $audits + $audits->setResource('user/' . $profile->getId()); + + $usage->setParam('users.update', 1); + + $events ->setParam('userId', $profile->getId()) - ->setParam('event', 'account.recovery.update') - ->setParam('resource', 'user/' . $profile->getId()) + ->setParam('tokenId', $recoveryDocument->getId()) ; - $usage - ->setParam('users.update', 1) - ; $response->dynamic($recoveryDocument, Response::MODEL_TOKEN); }); @@ -2030,7 +1989,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') @@ -2058,9 +2017,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'))) { @@ -2099,37 +2058,29 @@ 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) + ->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 - )) - ; - - $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()) + ->setParam('tokenId', $verification->getId()) + ->setPayload($response->output( + $verification->setAttribute('secret', $verificationSecret), + Response::MODEL_TOKEN + )) ; - $usage - ->setParam('users.update', 1) - ; + // Hide secret for clients + $verification->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? $verificationSecret : ''); + + $audits->setResource('user/' . $user->getId()); + $usage->setParam('users.update', 1); + $response->setStatusCode(Response::STATUS_CODE_CREATED); $response->dynamic($verification, Response::MODEL_TOKEN); }); @@ -2138,7 +2089,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') @@ -2155,12 +2106,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\Event\Audit $audits */ /** @var Appwrite\Stats\Stats $usage */ + /** @var Appwrite\Event\Event $events */ $profile = Authorization::skip(fn() => $dbForProject->getDocument('users', $userId)); @@ -2178,7 +2131,7 @@ App::put('/v1/account/verification') Authorization::setRole('user:' . $profile->getId()); $profile = $dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('emailVerification', true)); - + $verificationDocument = $dbForProject->getDocument('tokens', $verification); /** @@ -2188,14 +2141,14 @@ App::put('/v1/account/verification') $dbForProject->deleteDocument('tokens', $verification); $dbForProject->deleteCachedDocument('users', $profile->getId()); - $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); + + $events + ->setParam('userId', $user->getId()) + ->setParam('tokenId', $verificationDocument->getId()) ; - $usage - ->setParam('users.update', 1) - ; $response->dynamic($verificationDocument, Response::MODEL_TOKEN); }); diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 2e77e165a..7277044ae 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -34,6 +34,8 @@ use Appwrite\Utopia\Database\Validator\Queries as QueriesValidator; use Appwrite\Utopia\Database\Validator\OrderAttributes as OrderAttributesValidator; 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; @@ -44,13 +46,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', ''); @@ -114,22 +116,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 + ->setContext($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); @@ -140,7 +143,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') @@ -158,11 +161,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; @@ -188,11 +193,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); @@ -582,7 +587,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') @@ -600,11 +605,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); @@ -634,14 +641,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); }); @@ -649,7 +656,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') @@ -667,8 +674,9 @@ 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\Event\Delete $deletes */ + /** @var Appwrite\Stats\Stats $usage */ $collection = $dbForProject->getDocument('collections', $collectionId); @@ -683,29 +691,29 @@ App::delete('/v1/database/collections/:collectionId') $dbForProject->deleteCachedCollection('collection_' . $collection->getInternalId()); $deletes - ->setParam('type', DELETE_TYPE_DOCUMENT) - ->setParam('document', $collection) + ->setType(DELETE_TYPE_DOCUMENT) + ->setDocument($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()) ; + $usage->setParam('database.collections.delete', 1); + $response->noContent(); }); 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') @@ -725,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); @@ -745,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); }); @@ -753,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]) @@ -772,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([ @@ -787,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); }); @@ -795,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]) @@ -815,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; @@ -846,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); }); @@ -854,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]) @@ -873,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([ @@ -888,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); }); @@ -896,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]) @@ -915,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, @@ -929,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); }); @@ -937,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]) @@ -958,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); @@ -993,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', []); @@ -1008,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]) @@ -1028,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); @@ -1067,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', []); @@ -1082,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]) @@ -1101,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, @@ -1115,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); }); @@ -1222,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') @@ -1240,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); @@ -1266,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); @@ -1292,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()) + ->setContext($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(); @@ -1307,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') @@ -1326,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); @@ -1394,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()) + ->setContext($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); @@ -1494,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') @@ -1512,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); @@ -1537,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()) + ->setContext($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(); @@ -1560,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') @@ -1585,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 */ @@ -1659,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()) + ->setContext($collection) + ; $usage ->setParam('database.documents.create', 1) @@ -1667,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); @@ -1946,7 +1982,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') @@ -1969,7 +2005,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 */ @@ -2007,7 +2043,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); } @@ -2061,17 +2097,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()) + ->setContext($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); @@ -2081,7 +2120,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') @@ -2101,8 +2140,8 @@ 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\Event $deletes */ + /** @var Appwrite\Event\Audit $audits */ + /** @var Appwrite\Event\Delete $deletes */ /** @var Appwrite\Stats\Stats $usage */ /** @var string $mode */ @@ -2150,24 +2189,25 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId') $document->setAttribute('$collection', $collectionId); $deletes - ->setParam('type', DELETE_TYPE_AUDIT) - ->setParam('document', $document) + ->setType(DELETE_TYPE_AUDIT) + ->setDocument($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()) + ->setContext($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 2a80c9bb9..f7de8362a 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -2,7 +2,9 @@ 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; use Appwrite\Utopia\Database\Validator\CustomId; use Utopia\Database\Validator\UID; @@ -34,7 +36,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 +49,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 +79,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 +150,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 +208,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 +229,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 +247,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 +260,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 +271,7 @@ App::get('/v1/functions/:functionId/usage') $backfill--; } $stats[$metric] = array_reverse($stats[$metric]); - } + } }); $usage = new Document([ @@ -283,7 +289,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 +301,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 Utopia\Database\Document $user */ + /** @var Appwrite\Event\Event $eventsInstance */ $function = $dbForProject->getDocument('functions', $functionId); @@ -331,16 +339,19 @@ 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()); + $response->dynamic($function, Response::MODEL_FUNCTION); }); @@ -348,7 +359,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 +372,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); @@ -396,14 +409,18 @@ 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 + ->setParam('functionId', $function->getId()) + ->setParam('deploymentId', $deployment->getId()); + $response->dynamic($function, Response::MODEL_FUNCTION); }); @@ -411,7 +428,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 +439,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\Delete $deletes */ + /** @var Appwrite\Event\Event $events */ $function = $dbForProject->getDocument('functions', $functionId); @@ -438,9 +457,10 @@ App::delete('/v1/functions/:functionId') } $deletes - ->setParam('type', DELETE_TYPE_DOCUMENT) - ->setParam('document', $function) - ; + ->setType(DELETE_TYPE_DOCUMENT) + ->setDocument($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,17 +487,17 @@ 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\Database\Document $project */ + /** @var Appwrite\Event\Event $events */ + /** @var Utopia\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()) { @@ -596,19 +616,18 @@ 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)) - ; + $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 +651,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 +765,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 +778,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\Delete $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 +808,22 @@ 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) - ; + ->setType(DELETE_TYPE_DOCUMENT) + ->setDocument($deployment); $response->noContent(); }); @@ -805,7 +832,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 +849,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 +871,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 +882,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 +899,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 +921,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 +937,26 @@ App::post('/v1/functions/:functionId/executions') } } + $events + ->setParam('functionId', $function->getId()) + ->setParam('executionId', $execution->getId()) + ->setContext($function); + 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', - 'data' => $data, - 'userId' => $user->getId(), - 'jwt' => $jwt, - ]); + $event = new Func(); + $event + ->setType('http') + ->setExecution($execution) + ->setFunction($function) + ->setData($data) + ->setJWT($jwt) + ->setProject($project) + ->setUser($user); + + $event->trigger(); $response->setStatusCode(Response::STATUS_CODE_CREATED); + return $response->dynamic($execution, Response::MODEL_EXECUTION); } @@ -966,7 +1003,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 +1033,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 +1083,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 +1106,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 +1119,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 +1137,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,13 +1147,18 @@ App::post('/v1/functions/:functionId/deployments/:deploymentId/builds/:buildId') throw new Exception('Build not failed', 400, Exception::BUILD_IN_PROGRESS); } - // 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 - ]); + $events + ->setParam('functionId', $function->getId()) + ->setParam('deploymentId', $deployment->getId()); + + // Retry the build + $buildEvent = new Build(); + $buildEvent + ->setType(BUILD_TYPE_RETRY) + ->setResource($function) + ->setDeployment($deployment) + ->setProject($project) + ->trigger(); $response->noContent(); - }); \ No newline at end of file + }); diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index a9267da13..80bc0c5f7 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -2,7 +2,9 @@ use Appwrite\Auth\Auth; use Appwrite\Auth\Validator\Password; -use Appwrite\Event\Event; +use Appwrite\Event\Certificate; +use Appwrite\Event\Delete; +use Appwrite\Event\Validator\Event; use Appwrite\Network\Validator\CNAME; use Appwrite\Network\Validator\Domain as DomainValidator; use Appwrite\Network\Validator\Origin; @@ -523,7 +525,7 @@ App::delete('/v1/projects/:projectId') ->inject('user') ->inject('dbForConsole') ->inject('deletes') - ->action(function (string $projectId, string $password, Response $response, Document $user, Database $dbForConsole, Event $deletes) { + ->action(function (string $projectId, string $password, Response $response, Document $user, Database $dbForConsole, Delete $deletes) { if (!Auth::passwordVerify($password, $user->getAttribute('password'))) { // Double check user password throw new Exception('Invalid credentials', 401, Exception::USER_INVALID_CREDENTIALS); @@ -536,8 +538,8 @@ App::delete('/v1/projects/:projectId') } $deletes - ->setParam('type', DELETE_TYPE_DOCUMENT) - ->setParam('document', $project) + ->setType(DELETE_TYPE_DOCUMENT) + ->setDocument($project) ; if (!$dbForConsole->deleteDocument('teams', $project->getAttribute('teamId', null))) { @@ -565,7 +567,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) @@ -681,7 +683,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) @@ -1347,9 +1349,10 @@ App::patch('/v1/projects/:projectId/domains/:domainId/verification') $dbForConsole->deleteCachedDocument('projects', $project->getId()); // Issue a TLS certificate when domain is verified - Resque::enqueue(Event::CERTIFICATES_QUEUE_NAME, Event::CERTIFICATES_CLASS_NAME, [ - 'domain' => $domain->getAttribute('domain'), - ]); + $event = new Certificate(); + $event + ->setDomain($domain) + ->trigger(); $response->dynamic($domain, Response::MODEL_DOMAIN); }); @@ -1368,7 +1371,7 @@ App::delete('/v1/projects/:projectId/domains/:domainId') ->inject('response') ->inject('dbForConsole') ->inject('deletes') - ->action(function (string $projectId, string $domainId, Response $response, Database $dbForConsole, $deletes) { + ->action(function (string $projectId, string $domainId, Response $response, Database $dbForConsole, Delete $deletes) { $project = $dbForConsole->getDocument('projects', $projectId); @@ -1390,9 +1393,8 @@ App::delete('/v1/projects/:projectId/domains/:domainId') $dbForConsole->deleteCachedDocument('projects', $project->getId()); $deletes - ->setParam('type', DELETE_TYPE_CERTIFICATES) - ->setParam('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 91727bb30..c11c4b7fc 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -2,6 +2,8 @@ use Appwrite\Auth\Auth; use Appwrite\ClamAV\Network; +use Appwrite\Event\Audit; +use Appwrite\Event\Delete; use Appwrite\Event\Event; use Appwrite\Utopia\Database\Validator\CustomId; use Appwrite\OpenSSL\OpenSSL; @@ -43,7 +45,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') @@ -65,7 +67,8 @@ App::post('/v1/storage/buckets') ->inject('dbForProject') ->inject('audits') ->inject('usage') - ->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) { + ->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) { $bucketId = $bucketId === 'unique()' ? $dbForProject->getId() : $bucketId; try { @@ -126,9 +129,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); @@ -209,7 +215,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') @@ -231,21 +237,21 @@ App::put('/v1/storage/buckets/:bucketId') ->inject('dbForProject') ->inject('audits') ->inject('usage') - ->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) { - + ->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, Audit $audits, Stats $usage, Event $events) { $bucket = $dbForProject->getDocument('buckets', $bucketId); if ($bucket->isEmpty()) { 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) @@ -260,9 +266,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); @@ -274,7 +283,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') @@ -288,8 +297,7 @@ App::delete('/v1/storage/buckets/:bucketId') ->inject('deletes') ->inject('events') ->inject('usage') - ->action(function (string $bucketId, Response $response, Database $dbForProject, Event $audits, Event $deletes, Event $events, Stats $usage) { - + ->action(function (string $bucketId, Response $response, Database $dbForProject, Audit $audits, Delete $deletes, Event $events, Stats $usage) { $bucket = $dbForProject->getDocument('buckets', $bucketId); if ($bucket->isEmpty()) { @@ -301,18 +309,17 @@ App::delete('/v1/storage/buckets/:bucketId') } $deletes - ->setParam('type', DELETE_TYPE_DOCUMENT) - ->setParam('document', $bucket) - ; + ->setType(DELETE_TYPE_DOCUMENT) + ->setDocument($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); @@ -325,7 +332,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') @@ -350,8 +357,7 @@ App::post('/v1/storage/buckets/:bucketId/files') ->inject('mode') ->inject('deviceFiles') ->inject('deviceLocal') - ->action(function (string $bucketId, string $fileId, array $file, ?array $read, ?array $write, Request $request, Response $response, Database $dbForProject, Document $user, Event $audits, Stats $usage, Event $events, string $mode, Device $deviceFiles, Device $deviceLocal) { - + ->action(function (string $bucketId, string $fileId, array $file, ?array $read, ?array $write, Request $request, Response $response, Database $dbForProject, Document $user, Audit $audits, Stats $usage, Event $events, string $mode, Device $deviceFiles, Device $deviceLocal) { $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); if ($bucket->isEmpty() @@ -557,9 +563,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); } @@ -579,9 +583,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); } @@ -594,8 +596,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 @@ -627,9 +628,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); } @@ -639,9 +638,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); } @@ -653,13 +650,16 @@ App::post('/v1/storage/buckets/:bucketId/files') } } - $events->setParam('bucket', $bucket->getArrayCopy()); + $events + ->setParam('bucketId', $bucket->getId()) + ->setParam('fileId', $file->getId()) + ->setContext($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') @@ -1271,7 +1271,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') @@ -1290,8 +1290,7 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId') ->inject('usage') ->inject('mode') ->inject('events') - ->action(function (string $bucketId, string $fileId, ?array $read, ?array $write,Response $response, Database $dbForProject, Document $user, Event $audits, Stats $usage, string $mode, Event $events) { - + ->action(function (string $bucketId, string $fileId, ?array $read, ?array $write, Response $response, Database $dbForProject, Document $user, Audit $audits, Stats $usage, string $mode, Event $events) { $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); $read = (is_null($read) && !$user->isEmpty()) ? ['user:' . $user->getId()] : $read ?? []; // By default set read permissions for user $write = (is_null($write) && !$user->isEmpty()) ? ['user:' . $user->getId()] : $write ?? []; @@ -1346,13 +1345,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()) + ->setContext($bucket) ; + $audits->setResource('file/' . $file->getId()); + $usage ->setParam('storage.files.update', 1) ->setParam('bucketId', $bucketId) @@ -1366,7 +1366,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') @@ -1383,8 +1383,7 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId') ->inject('mode') ->inject('deviceFiles') ->inject('project') - ->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, Event $events, Event $audits, Stats $usage, string $mode, Device $deviceFiles, Document $project) { - + ->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, Event $events, Audit $audits, Stats $usage, string $mode, Device $deviceFiles, Document $project) { $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); if ($bucket->isEmpty() @@ -1438,10 +1437,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) @@ -1450,8 +1446,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()) + ->setContext($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 a6bb58ba3..c504fe40e 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -2,7 +2,10 @@ use Appwrite\Auth\Auth; use Appwrite\Detector\Detector; +use Appwrite\Event\Audit as EventAudit; +use Appwrite\Event\Delete; use Appwrite\Event\Event; +use Appwrite\Event\Mail; use Appwrite\Extend\Exception; use Appwrite\Network\Validator\Email; use Appwrite\Network\Validator\Host; @@ -31,7 +34,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') @@ -84,6 +87,8 @@ App::post('/v1/teams') $dbForProject->deleteCachedDocument('users', $user->getId()); } + $events->setParam('teamId', $team->getId()); + if (!empty($user->getId())) { $events->setParam('userId', $user->getId()); } @@ -170,7 +175,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') @@ -183,8 +188,9 @@ App::put('/v1/teams/:teamId') ->param('name', null, new Text(128), 'New team name. Max length: 128 chars.') ->inject('response') ->inject('dbForProject') + ->inject('events') ->inject('audits') - ->action(function (string $teamId, string $name, Response $response, Database $dbForProject, Event $audits) { + ->action(function (string $teamId, string $name, Response $response, Database $dbForProject, Event $events, EventAudit $audits) { $team = $dbForProject->getDocument('teams', $teamId); @@ -197,11 +203,8 @@ App::put('/v1/teams/:teamId') ->setAttribute('search', implode(' ', [$teamId, $name])) ); - $audits - ->setParam('event', 'teams.update') - ->setParam('resource', 'team/'.$teamId) - ->setParam('data', $team->getArrayCopy()) - ; + $events->setParam('teamId', $team->getId()); + $audits->setResource('team/' . $team->getId()); $response->dynamic($team, Response::MODEL_TEAM); }); @@ -209,7 +212,7 @@ App::put('/v1/teams/:teamId') 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') @@ -223,7 +226,7 @@ App::delete('/v1/teams/:teamId') ->inject('events') ->inject('deletes') ->inject('audits') - ->action(function (string $teamId, Response $response, Database $dbForProject, Event $events, Event $deletes, Event $audits) { + ->action(function (string $teamId, Response $response, Database $dbForProject, Event $events, Delete $deletes, EventAudit $audits) { $team = $dbForProject->getDocument('teams', $teamId); @@ -247,12 +250,12 @@ App::delete('/v1/teams/:teamId') } $deletes - ->setParam('type', DELETE_TYPE_DOCUMENT) - ->setParam('document', $team) - ; + ->setType(DELETE_TYPE_DOCUMENT) + ->setDocument($team); $events - ->setParam('eventData', $response->output($team, Response::MODEL_TEAM)) + ->setParam('teamId', $team->getId()) + ->setPayload($response->output($team, Response::MODEL_TEAM)) ; $audits @@ -267,7 +270,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]) @@ -290,7 +293,8 @@ App::post('/v1/teams/:teamId/memberships') ->inject('locale') ->inject('audits') ->inject('mails') - ->action(function (string $teamId, string $email, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Event $audits, Event $mails) { + ->inject('events') + ->action(function (string $teamId, string $email, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, EventAudit $audits, Mail $mails, Event $events) { if(empty(App::getEnv('_APP_SMTP_HOST'))) { throw new Exception('SMTP Disabled', 503, Exception::GENERAL_SMTP_DISABLED); @@ -399,24 +403,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); @@ -545,7 +549,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') @@ -562,7 +566,8 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId') ->inject('user') ->inject('dbForProject') ->inject('audits') - ->action(function (string $teamId, string $membershipId, array $roles, Request $request, Response $response, Document $user, Database $dbForProject, Event $audits) { + ->inject('events') + ->action(function (string $teamId, string $membershipId, array $roles, Request $request, Response $response, Document $user, Database $dbForProject, EventAudit $audits, Event $events) { $team = $dbForProject->getDocument('teams', $teamId); if ($team->isEmpty()) { @@ -596,13 +601,13 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId') /** * Replace membership on profile */ - $dbForProject->deleteCachedDocument('users', $profile->getId()); - $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 @@ -615,7 +620,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') @@ -634,8 +639,8 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') ->inject('dbForProject') ->inject('geodb') ->inject('audits') - ->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Reader $geodb, Event $audits) { - + ->inject('events') + ->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Reader $geodb, EventAudit $audits, Event $events) { $protocol = $request->getProtocol(); $membership = $dbForProject->getDocument('memberships', $membershipId); @@ -658,7 +663,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); } @@ -718,10 +723,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')) { @@ -744,7 +750,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') @@ -758,7 +764,7 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId') ->inject('dbForProject') ->inject('audits') ->inject('events') - ->action(function (string $teamId, string $membershipId, Response $response, Database $dbForProject, Event $audits, Event $events) { + ->action(function (string $teamId, string $membershipId, Response $response, Database $dbForProject, EventAudit $audits, Event $events) { $membership = $dbForProject->getDocument('memberships', $membershipId); @@ -797,14 +803,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 2f33098bc..6e96f7264 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); }); @@ -281,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 = []; @@ -348,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); }); @@ -356,7 +344,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 +358,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 +376,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 +401,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 +419,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 +444,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\Audit $audits */ + /** @var Appwrite\Event\Event $events */ $user = $dbForProject->getDocument('users', $userId); @@ -461,9 +465,11 @@ App::patch('/v1/users/:userId/name') $user = $dbForProject->updateDocument('users', $user->getId(), $user); $audits + ->setResource('user/'.$user->getId()) + ; + + $events ->setParam('userId', $user->getId()) - ->setParam('event', 'users.update.name') - ->setParam('resource', 'user/'.$user->getId()) ; $response->dynamic($user, Response::MODEL_USER); @@ -472,7 +478,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') @@ -486,10 +492,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\Audit $audits */ + /** @var Appwrite\Event\Event $events */ $user = $dbForProject->getDocument('users', $userId); @@ -504,9 +512,11 @@ App::patch('/v1/users/:userId/password') $user = $dbForProject->updateDocument('users', $user->getId(), $user); $audits + ->setResource('user/'.$user->getId()) + ; + + $events ->setParam('userId', $user->getId()) - ->setParam('event', 'users.update.password') - ->setParam('resource', 'user/'.$user->getId()) ; $response->dynamic($user, Response::MODEL_USER); @@ -515,7 +525,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') @@ -529,10 +539,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\Audit $audits */ + /** @var Appwrite\Event\Event $events */ $user = $dbForProject->getDocument('users', $userId); @@ -558,10 +570,13 @@ App::patch('/v1/users/:userId/email') throw new Exception('Email already exists', 409, Exception::USER_EMAIL_ALREADY_EXISTS); } + $audits + ->setResource('user/'.$user->getId()) + ; + + $events ->setParam('userId', $user->getId()) - ->setParam('event', 'users.update.email') - ->setParam('resource', 'user/'.$user->getId()) ; $response->dynamic($user, Response::MODEL_USER); @@ -570,7 +585,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') @@ -584,10 +599,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); @@ -600,13 +617,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') @@ -635,29 +657,30 @@ App::delete('/v1/users/:userId/sessions/:sessionId') $session = $dbForProject->getDocument('sessions', $sessionId); if($session->isEmpty()) { - throw new Exception('User not found', 404, Exception::USER_SESSION_NOT_FOUND); + throw new Exception('Session not found', 404, Exception::USER_SESSION_NOT_FOUND); } $dbForProject->deleteDocument('sessions', $session->getId()); - $dbForProject->deleteCachedDocument('users', $user->getId()); - $events - ->setParam('eventData', $response->output($user, Response::MODEL_USER)) - ; $usage ->setParam('users.update', 1) ->setParam('users.sessions.delete', 1) ; + $events + ->setParam('userId', $user->getId()) + ->setParam('sessionId', $sessionId) + ; + $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') @@ -686,25 +709,28 @@ 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()); $events - ->setParam('eventData', $response->output($user, Response::MODEL_USER)) + ->setParam('userId', $user->getId()) + ->setPayload($response->output($user, Response::MODEL_USER)) ; $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') @@ -722,9 +748,9 @@ 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); if ($user->isEmpty() || $user->getAttribute('deleted')) { @@ -735,7 +761,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; @@ -751,12 +777,13 @@ App::delete('/v1/users/:userId') $dbForProject->updateDocument('users', $userId, $user); $deletes - ->setParam('type', DELETE_TYPE_DOCUMENT) - ->setParam('document', $clone) + ->setType(DELETE_TYPE_DOCUMENT) + ->setDocument($clone) ; $events - ->setParam('eventData', $response->output($clone, Response::MODEL_USER)) + ->setParam('userId', $user->getId()) + ->setPayload($response->output($clone, Response::MODEL_USER)) ; $usage diff --git a/app/controllers/general.php b/app/controllers/general.php index b9c45443b..5c682efb4 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -12,7 +12,7 @@ use Appwrite\Extend\Exception; use Utopia\Config\Config; use Utopia\Domains\Domain; use Appwrite\Auth\Auth; -use Appwrite\Event\Event; +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; @@ -98,18 +98,18 @@ App::init(function ($utopia, $request, $response, $console, $project, $dbForCons 'verification' => false, 'certificateId' => null, ]); - + $domainDocument = $dbForConsole->createDocument('domains', $domainDocument); Console::info('Issuing a TLS certificate for the main domain (' . $domain->get() . ') in a few seconds...'); - - Resque::enqueue(Event::CERTIFICATES_QUEUE_NAME, Event::CERTIFICATES_CLASS_NAME, [ - 'domain' => $domain->get() - ]); + + (new Certificate()) + ->setDomain($domainDocument) + ->trigger(); } } - $domains[$domain->get()] = true; + Authorization::reset(); // ensure authorization is re-enabled } Config::setParam('domains', $domains); diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index c5c6da616..e4ab00c04 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -1,19 +1,16 @@ 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') + ->setEvent($route->getLabel('event', '')) + ->setProject($project) + ->setUser($user) + ; + + $mails + ->setProject($project) + ->setUser($user) ; $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', []) + ->setMode($mode) + ->setUserAgent($request->getUserAgent('')) + ->setIP($request->getIP()) + ->setEvent($route->getLabel('event', '')) + ->setProject($project) + ->setUser($user) ; $usage @@ -121,15 +115,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 */ @@ -184,56 +173,66 @@ 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\Delete $deletes */ + /** @var Appwrite\Event\Database $database */ /** @var bool $mode */ + /** @var Utopia\Database\Database $dbForProject */ - if (!empty($events->getParam('event'))) { - if (empty($events->getParam('eventData'))) { - $events->setParam('eventData', $response->getPayload()); + if (!empty($events->getEvent())) { + if (empty($events->getPayload())) { + $events->setPayload($response->getPayload()); } - - $webhooks = clone $events; - $functions = clone $events; - - $webhooks - ->setQueue('v1-webhooks') - ->setClass('WebhooksV1') + /** + * Trigger functions. + */ + $events + ->setClass(Event::FUNCTIONS_CLASS_NAME) + ->setQueue(Event::FUNCTIONS_QUEUE_NAME) ->trigger(); - $functions - ->setQueue('v1-functions') - ->setClass('FunctionsV1') + /** + * Trigger webhooks. + */ + $events + ->setClass(Event::WEBHOOK_CLASS_NAME) + ->setQueue(Event::WEBHOOK_QUEUE_NAME) ->trigger(); + /** + * Trigger realtime. + */ if ($project->getId() !== 'console') { - $payload = new Document($response->getPayload()); - $collection = new Document($events->getParam('collection') ?? []); - $bucket = new Document($events->getParam('bucket') ?? []); + $allEvents = Event::generateEvents($events->getEvent(), $events->getParams()); + $payload = new Document($events->getPayload()); + $context = $events->getContext() ?? false; + + $collection = ($context && $context->getCollection() === 'collections') ? $context : null; + $bucket = ($context && $context->getCollection() === 'buckets') ? $context : null; $target = Realtime::fromPayload( - event: $events->getParam('event'), - payload: $payload, - project: $project, + // Pass first, most verbose event pattern + event: $allEvents[0], + payload: $payload, + project: $project, collection: $collection, bucket: $bucket, ); Realtime::send( - $target['projectId'] ?? $project->getId(), - $response->getPayload(), - $events->getParam('event'), - $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') ] @@ -241,15 +240,18 @@ App::shutdown(function ($utopia, $request, $response, $project, $events, $audits } } - if (!empty($audits->getParam('event'))) { + 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->getType())) { $deletes->trigger(); } - if (!empty($database->getParam('type')) && !empty($database->getParam('document'))) { + if (!empty($database->getType())) { $database->trigger(); } @@ -262,8 +264,7 @@ App::shutdown(function ($utopia, $request, $response, $project, $events, $audits $usage ->setParam('networkRequestSize', $request->getSize() + $usage->getParam('storage')) ->setParam('networkResponseSize', $response->getSize()) - ->submit() - ; + ->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/controllers/web/console.php b/app/controllers/web/console.php index 12cb625a8..58bc905db 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/init.php b/app/init.php index 269d860fe..8d5f41b01 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; @@ -70,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'; @@ -642,10 +646,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/realtime.php b/app/realtime.php index 76b9af5a7..3f996b0bc 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); @@ -340,10 +336,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...'); @@ -361,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 */ @@ -512,7 +507,7 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re } switch ($message['type']) { - /** + /** * This type is used to authenticate. */ case 'authentication': diff --git a/app/tasks/maintenance.php b/app/tasks/maintenance.php index 056ee59fc..3bb528c4d 100644 --- a/app/tasks/maintenance.php +++ b/app/tasks/maintenance.php @@ -3,13 +3,15 @@ global $cli; global $register; -use Appwrite\Event\Event; +use Appwrite\Event\Certificate; +use Appwrite\Event\Delete; use Utopia\App; use Utopia\Cache\Cache; use Utopia\CLI\Console; use Utopia\Database\Adapter\MariaDB; use Utopia\Database\Database; use Utopia\Cache\Adapter\Redis as RedisCache; +use Utopia\Database\Document; use Utopia\Database\Query; function getConsoleDB(): Database @@ -26,10 +28,10 @@ function getConsoleDB(): Database $database->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite')); $database->setNamespace('_console'); // Main DB 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); } @@ -43,66 +45,68 @@ $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 - ]); + (new Delete()) + ->setType(DELETE_TYPE_EXECUTIONS) + ->setTimestamp(time() - $interval) + ->trigger(); } - 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 - ]); + (new Delete()) + ->setType(DELETE_TYPE_ABUSE) + ->setTimestamp(time() - $interval) + ->trigger(); } - 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 - ]); + (new Delete()) + ->setType(DELETE_TYPE_AUDIT) + ->setTimestamp(time() - $interval) + ->trigger(); } - 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, - ]); + (new Delete()) + ->setType(DELETE_TYPE_USAGE) + ->setTimestamp1d(time() - $interval1d) + ->setTimestamp30m(time() - $interval30m) + ->trigger(); } - function notifyDeleteConnections() + function notifyDeleteConnections() { - Resque::enqueue(Event::DELETE_QUEUE_NAME, Event::DELETE_CLASS_NAME, [ - 'type' => DELETE_TYPE_REALTIME, - 'timestamp' => time() - 60 - ]); + (new Delete()) + ->setType(DELETE_TYPE_REALTIME) + ->setTimestamp(time() - 60) + ->trigger(); } function renewCertificates($dbForConsole) { $time = date('d-m-Y H:i:s', time()); - /** @var Utopia\Database\Database $dbForConsole */ - $certificates = $dbForConsole->find('certificates', [ new Query('attempts', Query::TYPE_LESSEREQUAL, [5]), // Maximum 5 attempts new Query('renewDate', Query::TYPE_LESSEREQUAL, [\time()]) // includes 60 days cooldown (we have 30 days to renew) ], 200); // Limit 200 comes from LetsEncrypt (300 orders per 3 hours, keeping some for new domains) - if(\count($certificates) > 0) { + + if (\count($certificates) > 0) { Console::info("[{$time}] Found " . \count($certificates) . " certificates for renewal, scheduling jobs."); + $event = new Certificate(); foreach ($certificates as $certificate) { - Resque::enqueue(Event::CERTIFICATES_QUEUE_NAME, Event::CERTIFICATES_CLASS_NAME, [ - 'domain' => $certificate->getAttribute('domain'), - ]); + $event + ->setDomain(new Document([ + 'domain' => $certificate->getAttribute('domain') + ])) + ->trigger(); } } else { Console::info("[{$time}] No certificates for renewal."); @@ -114,10 +118,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) { $database = getConsoleDB(); $time = date('d-m-Y H:i:s', time()); @@ -127,7 +131,6 @@ $cli notifyDeleteAuditLogs($auditLogRetention); notifyDeleteUsageStats($usageStatsRetention30m, $usageStatsRetention1d); notifyDeleteConnections(); - renewCertificates($database); }, $interval); - }); \ No newline at end of file + }); diff --git a/app/tasks/sdks.php b/app/tasks/sdks.php index 166eec495..1b3b8411a 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/app/tasks/ssl.php b/app/tasks/ssl.php index 743be92a9..345da1f22 100644 --- a/app/tasks/ssl.php +++ b/app/tasks/ssl.php @@ -2,9 +2,10 @@ global $cli; -use Appwrite\Event\Event; +use Appwrite\Event\Certificate; use Utopia\App; use Utopia\CLI\Console; +use Utopia\Database\Document; use Utopia\Validator\Hostname; $cli @@ -14,9 +15,10 @@ $cli ->action(function ($domain) { Console::success('Scheduling a job to issue a TLS certificate for domain: ' . $domain); - // Scheduje a job - Resque::enqueue(Event::CERTIFICATES_QUEUE_NAME, Event::CERTIFICATES_CLASS_NAME, [ - 'domain' => $domain, - 'skipCheck' => true - ]); - }); \ No newline at end of file + (new Certificate()) + ->setDomain(new Document([ + 'domain' => $domain + ])) + ->setSkipRenewCheck(true) + ->trigger(); + }); diff --git a/app/workers/audits.php b/app/workers/audits.php index d83c832c3..696eb6df5 100644 --- a/app/workers/audits.php +++ b/app/workers/audits.php @@ -1,17 +1,20 @@ args['projectId']; - $userId = $this->args['userId']; - $userName = $this->args['userName']; - $userEmail = $this->args['userEmail']; + $events = $this->args['events']; + $payload = $this->args['payload']; $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); - $audit->log($userId, $event, $resource, $userAgent, $ip, '', [ - 'userName' => $userName, - 'userEmail' => $userEmail, - 'mode' => $mode, - 'data' => $data, - ]); + $user = new Document($this->args['user']); + $project = new Document($this->args['project']); + + $userName = $user->getAttribute('name', ''); + $userEmail = $user->getAttribute('email', ''); + + $dbForProject = $this->getProjectDB($project->getId()); + $audit = new Audit($dbForProject); + $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 { - // ... Remove environment for this job } } diff --git a/app/workers/builds.php b/app/workers/builds.php index 031ffd84c..c1affa590 100644 --- a/app/workers/builds.php +++ b/app/workers/builds.php @@ -1,7 +1,9 @@ executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST')); } 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: @@ -56,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); - - $function = $dbForProject->getDocument('functions', $functionId); + $dbForProject = $this->getProjectDB($project->getId()); + + $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); } @@ -89,7 +87,7 @@ class BuildsV1 extends Worker '$read' => [], '$write' => [], 'startTime' => $startTime, - 'deploymentId' => $deploymentId, + 'deploymentId' => $deployment->getId(), 'status' => 'processing', 'outputPath' => '', 'runtime' => $function->getAttribute('runtime'), @@ -101,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); } @@ -110,12 +108,39 @@ class BuildsV1 extends Worker $build->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( + // Pass first, most verbose event pattern + event: $allEvents[0], + payload: $build, + project: $project + ); + Realtime::send( projectId: 'console', payload: $build->getArrayCopy(), - event: 'functions.deployments.update', + events: $allEvents, channels: $target['channels'], roles: $target['roles'] ); @@ -126,13 +151,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, @@ -156,7 +181,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 */ @@ -164,8 +189,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); @@ -177,18 +201,25 @@ class BuildsV1 extends Worker $build = $dbForProject->updateDocument('builds', $buildId, $build); /** - * Send realtime Event + * Send realtime Event */ - $target = Realtime::fromPayload('functions.deployments.update', $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(), - event: 'functions.deployments.update', + events: $allEvents, channels: $target['channels'], roles: $target['roles'] ); } } - public function shutdown(): void {} + public function shutdown(): void + { + } } diff --git a/app/workers/certificates.php b/app/workers/certificates.php index f932e4b8f..888f4de8e 100644 --- a/app/workers/certificates.php +++ b/app/workers/certificates.php @@ -11,14 +11,14 @@ use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; use Utopia\Domains\Domain; -require_once __DIR__.'/../init.php'; +require_once __DIR__ . '/../init.php'; Console::title('Certificates V1 Worker'); Console::success(APP_NAME . ' certificates worker v1 has started'); class CertificatesV1 extends Worker { - + /** * Database connection shared across all methods of this file * @@ -26,13 +26,13 @@ class CertificatesV1 extends Worker */ private Database $dbForConsole; - public function getName(): string { + public function getName(): string + { return "certificates"; } public function init(): void { - } public function run(): void @@ -73,16 +73,22 @@ class CertificatesV1 extends Worker try { // Read arguments - $domain = $this->args['domain']; // String of domain (hostname) - $skipCheck = $this->args['skipCheck'] ?? false; // If true, we won't double-check expiry from cert file + $document = new Document($this->args['domain'] ?? []); + $skipCheck = $this->args['skipRenewCheck'] ?? false; // If true, we won't double-check expiry from cert file + + // Options + $domain = new Domain($document->getAttribute('domain')); + $expiry = 60 * 60 * 24 * 30 * 2; // 60 days + $safety = 60 * 60; // 1 hour + $renew = (\time() + $expiry); $domain = new Domain((!empty($domain)) ? $domain : ''); // Get current certificate - $certificate = $this->dbForConsole->findOne('certificates', [ new Query('domain', Query::TYPE_EQUAL, [$domain->get()]) ]); + $certificate = $this->dbForConsole->findOne('certificates', [new Query('domain', Query::TYPE_EQUAL, [$domain->get()])]); // If we don't have certificate for domain yet, let's create new document. At the end we save it - if(!$certificate) { + if (!$certificate) { $certificate = new Document(); $certificate->setAttribute('domain', $domain->get()); } @@ -92,16 +98,16 @@ class CertificatesV1 extends Worker if (empty($email)) { throw new Exception('You must set a valid security email address (_APP_SYSTEM_SECURITY_EMAIL_ADDRESS) to issue an SSL certificate.'); } - + // Validate domain and DNS records. Skip if job is forced - if(!$skipCheck) { + if (!$skipCheck) { $mainDomain = $this->getMainDomain(); $isMainDomain = !isset($mainDomain) || $domain->get() === $mainDomain; $this->validateDomain($domain, $isMainDomain); } // If certificate exists already, double-check expiry date. Skip if job is forced - if(!$skipCheck && !$this->isRenewRequired($domain->get())) { + if (!$skipCheck && !$this->isRenewRequired($domain->get())) { throw new Exception('Renew isn\'t required.'); } @@ -114,15 +120,15 @@ class CertificatesV1 extends Worker 'stdout' => $letsEncryptData['stdout'], 'stderr' => $letsEncryptData['stderr'], ])); - + // Give certificates to Traefik $this->applyCertificateFiles($domain->get(), $letsEncryptData); - + // Update certificate info stored in database $certificate->setAttribute('renewDate', $this->getRenewDate($domain->get())); $certificate->setAttribute('attempts', 0); $certificate->setAttribute('issueDate', \time()); - } catch(Throwable $e) { + } catch (Throwable $e) { // Set exception as log in certificate document $certificate->setAttribute('log', $e->getMessage()); @@ -155,13 +161,14 @@ class CertificatesV1 extends Worker * * @return void */ - private function saveCertificateDocument(string $domain, Document $certificate): void { + private function saveCertificateDocument(string $domain, Document $certificate): void + { // Check if update or insert required - $certificateDocument = $this->dbForConsole->findOne('certificates', [ new Query('domain', Query::TYPE_EQUAL, [$domain]) ]); + $certificateDocument = $this->dbForConsole->findOne('certificates', [new Query('domain', Query::TYPE_EQUAL, [$domain])]); if (!empty($certificateDocument) && !$certificateDocument->isEmpty()) { // Merge new data with current data $certificate = new Document(\array_merge($certificateDocument->getArrayCopy(), $certificate->getArrayCopy())); - + $certificate = $this->dbForConsole->updateDocument('certificates', $certificate->getId(), $certificate); } else { $certificate = $this->dbForConsole->createDocument('certificates', $certificate); @@ -176,12 +183,13 @@ class CertificatesV1 extends Worker * * @return null|string Returns main domain. If null, there is no main domain yet. */ - private function getMainDomain(): ?string { + private function getMainDomain(): ?string + { if (!empty(App::getEnv('_APP_DOMAIN', ''))) { return App::getEnv('_APP_DOMAIN', ''); } else { $domainDocument = $this->dbForConsole->findOne('domains', [], 0, ['_id'], ['ASC']); - if($domainDocument) { + if ($domainDocument) { return $domainDocument->getAttribute('domain'); } } @@ -199,7 +207,8 @@ class CertificatesV1 extends Worker * * @return void */ - private function validateDomain(Domain $domain, bool $isMainDomain): void { + private function validateDomain(Domain $domain, bool $isMainDomain): void + { if (empty($domain->get())) { throw new Exception('Missing certificate domain.'); } @@ -215,7 +224,7 @@ class CertificatesV1 extends Worker $target = new Domain(App::getEnv('_APP_DOMAIN_TARGET', '')); if (!$target->isKnown() || $target->isTest()) { - throw new Exception('Unreachable CNAME target ('.$target->get().'), please use a domain with a public suffix.'); + throw new Exception('Unreachable CNAME target (' . $target->get() . '), please use a domain with a public suffix.'); } // Verify domain with DNS records @@ -236,7 +245,8 @@ class CertificatesV1 extends Worker * * @return bool True, if certificate needs to be renewed */ - private function isRenewRequired(string $domain): bool { + private function isRenewRequired(string $domain): bool + { $certPath = APP_STORAGE_CERTIFICATES . '/' . $domain . '/cert.pem'; if (\file_exists($certPath)) { $validTo = null; @@ -249,7 +259,7 @@ class CertificatesV1 extends Worker } // LetsEncrypt allows renewal 30 days before expiry - $expiryInAdvance = (60*60*24*30); + $expiryInAdvance = (60 * 60 * 24 * 30); if ($validTo - $expiryInAdvance > \time()) { return false; } @@ -265,7 +275,8 @@ class CertificatesV1 extends Worker * * @return array Named array with keys 'stdout' and 'stderr', both string */ - private function issueCertificate(string $domain, string $email): array { + private function issueCertificate(string $domain, string $email): array + { $staging = (App::isProduction()) ? '' : ' --dry-run'; $stdout = ''; @@ -295,11 +306,12 @@ class CertificatesV1 extends Worker * * @return int */ - private function getRenewDate(string $domain): int { + private function getRenewDate(string $domain): int + { $certPath = APP_STORAGE_CERTIFICATES . '/' . $domain . '/cert.pem'; $certData = openssl_x509_parse(file_get_contents($certPath)); $validTo = $certData['validTo_time_t'] ?? 0; - $expiryInAdvance = (60*60*24*30); // 30 days + $expiryInAdvance = (60 * 60 * 24 * 30); // 30 days return $validTo - $expiryInAdvance; } @@ -311,7 +323,8 @@ class CertificatesV1 extends Worker * * @return void */ - private function applyCertificateFiles(string $domain, array $letsEncryptData): void { + private function applyCertificateFiles(string $domain, array $letsEncryptData): void + { // Prepare folder in storage for domain $path = APP_STORAGE_CERTIFICATES . '/' . $domain; if (!\is_readable($path)) { @@ -321,7 +334,7 @@ class CertificatesV1 extends Worker } // Move generated files from certbot into our storage - if(!@\rename('/etc/letsencrypt/live/'.$domain.'/cert.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/cert.pem')) { + if (!@\rename('/etc/letsencrypt/live/' . $domain . '/cert.pem', APP_STORAGE_CERTIFICATES . '/' . $domain . '/cert.pem')) { throw new Exception('Failed to rename certificate cert.pem. Let\'s Encrypt log: ' . $letsEncryptData['stderr'] . ' ; ' . $letsEncryptData['stdout']); } @@ -343,7 +356,7 @@ class CertificatesV1 extends Worker " - certFile: /storage/certificates/{$domain}/fullchain.pem", " keyFile: /storage/certificates/{$domain}/privkey.pem" ]); - + // Save configuration into Traefik using our new cert files if (!\file_put_contents(APP_STORAGE_CONFIG . '/' . $domain . '.yml', $config)) { throw new Exception('Failed to save Traefik configuration.'); @@ -359,7 +372,8 @@ class CertificatesV1 extends Worker * * @return void */ - private function notifyError(string $domain, string $errorMessage, int $attempt): void { + private function notifyError(string $domain, string $errorMessage, int $attempt): void + { // Log error into console Console::warning('Cannot renew domain (' . $domain . ') on attempt no. ' . $attempt . ' certificate: ' . $errorMessage); @@ -391,7 +405,8 @@ class CertificatesV1 extends Worker * * @return void */ - private function updateDomainDocuments(string $certificateId, string $domain): void { + private function updateDomainDocuments(string $certificateId, string $domain): void + { $domains = $this->dbForConsole->find('domains', [ new Query('domain', Query::TYPE_EQUAL, [$domain]) ], 1000); @@ -402,9 +417,9 @@ class CertificatesV1 extends Worker $this->dbForConsole->updateDocument('domains', $domainDocument->getId(), $domainDocument); - if($domainDocument->getAttribute('projectId')) { + if ($domainDocument->getAttribute('projectId')) { $this->dbForConsole->deleteCachedDocument('projects', $domainDocument->getAttribute('projectId')); } - } + } } } diff --git a/app/workers/database.php b/app/workers/database.php index 5fd3e16e2..a3451461c 100644 --- a/app/workers/database.php +++ b/app/workers/database.php @@ -1,5 +1,6 @@ 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 +37,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: @@ -71,12 +71,15 @@ class DatabaseV1 extends Worker $dbForConsole = $this->getConsoleDB(); $dbForProject = $this->getProjectDB($projectId); + $events = Event::generateEvents('collections.[collectionId].attributes.[attributeId].update', [ + 'collectionId' => $collection->getId(), + 'attributeId' => $attribute->getId() + ]); /** * Fetch attribute from the database, since with Resque float values are loosing informations. */ $attribute = $dbForProject->getDocument('attributes', $attribute->getId()); - $event = 'database.attributes.update'; $collectionId = $collection->getId(); $key = $attribute->getAttribute('key', ''); $type = $attribute->getAttribute('type', ''); @@ -99,12 +102,17 @@ 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( + // Pass first, most verbose event pattern + event: $events[0], + payload: $attribute, + project: $project + ); Realtime::send( projectId: 'console', payload: $attribute->getArrayCopy(), - event: $event, + events: $events, channels: $target['channels'], roles: $target['roles'], options: [ @@ -127,7 +135,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', ''); @@ -148,12 +159,17 @@ 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( + // Pass first, most verbose event pattern + event: $events[0], + payload: $attribute, + project: $project + ); Realtime::send( projectId: 'console', payload: $attribute->getArrayCopy(), - event: $event, + events: $events, channels: $target['channels'], roles: $target['roles'], options: [ @@ -228,7 +244,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', ''); @@ -246,12 +265,17 @@ 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( + // Pass first, most verbose event pattern + event: $events[0], + payload: $index, + project: $project + ); Realtime::send( projectId: 'console', payload: $index->getArrayCopy(), - event: $event, + events: $events, channels: $target['channels'], roles: $target['roles'], options: [ @@ -274,10 +298,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 { @@ -289,12 +315,17 @@ 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( + // Pass first, most verbose event pattern + event: $events[0], + payload: $index, + project: $project + ); Realtime::send( projectId: 'console', payload: $index->getArrayCopy(), - event: $event, + events: $events, channels: $target['channels'], roles: $target['roles'], options: [ @@ -304,6 +335,6 @@ class DatabaseV1 extends Worker ); } - $dbForProject->deleteCachedDocument('collections', $collectionId); + $dbForProject->deleteCachedDocument('collections', $collection->getId()); } } diff --git a/app/workers/deletes.php b/app/workers/deletes.php index 29b9bc5c4..295a82c71 100644 --- a/app/workers/deletes.php +++ b/app/workers/deletes.php @@ -41,8 +41,7 @@ class DeletesV1 extends Worker public function run(): void { - - $projectId = $this->args['projectId'] ?? ''; + $project = new Document($this->args['project'] ?? []); $type = $this->args['type'] ?? ''; switch (strval($type)) { @@ -51,25 +50,25 @@ class DeletesV1 extends Worker 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()); @@ -82,15 +81,15 @@ class DeletesV1 extends Worker 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']); } if (!$document->isEmpty()) { - $this->deleteAuditLogsByResource('document/' . $document->getId(), $projectId); + $this->deleteAuditLogsByResource('document/' . $document->getId(), $project->getId()); } break; @@ -207,7 +206,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(); // Delete all sessions of this user from the sessions table and update the sessions field of the user record diff --git a/app/workers/functions.php b/app/workers/functions.php index 96f12fddf..1a6ae838b 100644 --- a/app/workers/functions.php +++ b/app/workers/functions.php @@ -1,13 +1,13 @@ 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'] ?? ''; + $type = $this->args['type'] ?? ''; + $events = $this->args['events'] ?? []; + $project = new Document($this->args['project'] ?? []); + $user = new Document($this->args['user'] ?? []); + $payload = json_encode($this->args['payload'] ?? []); - $database = $this->getProjectDB($projectId); + $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) { - $events = $function->getAttribute('events', []); - - if (!\in_array($event, $events)) { - continue; - } - - Console::success('Iterating function: ' . $function->getAttribute('name')); - - $this->execute( - projectId: $projectId, - function: $function, - dbForProject: $database, - executionId: $executionId, - webhooks: $webhooks, - trigger: $trigger, - event: $event, - eventData: $eventData, - data: $data, - userId: $userId, - jwt: $jwt - ); - - Console::success('Triggered function: ' . $event); + foreach ($functions as $function) { + if (!array_intersect($events, $function->getAttribute('events', []))) { + continue; } + + Console::success('Iterating function: ' . $function->getAttribute('name')); + + $this->execute( + project: $project, + function: $function, + dbForProject: $database, + trigger: 'event', + // Pass first, most verbose event pattern + event: $events[0], + eventData: $payload, + user: $user + ); + + 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': + $jwt = $this->args['jwt'] ?? ''; + $data = $this->args['data'] ?? ''; + + $function = Authorization::skip(fn () => $database->getDocument('functions', $execution->getAttribute('functionId'))); + + $this->execute( + project: $project, + function: $function, + dbForProject: $database, + executionId: $execution->getId(), + trigger: 'http', + data: $data, + user: $user, + jwt: $jwt + ); break; case 'schedule': + $scheduleOriginal = $execution->getAttribute('scheduleOriginal', ''); + $function = Authorization::skip(fn () => $database->getDocument('functions', $execution->getAttribute('functionId'))); + /* * 1. Get Original Task * 2. Check for updates @@ -115,10 +133,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,61 +148,31 @@ 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(), [ - 'scheduleNext' => (int)$next, - ]))); - - if ($function === false) { - throw new Exception('Function update failed (' . $functionId . ')'); - } - return $function; - }); + $function = Authorization::skip(fn () => $database->updateDocument( + 'functions', + $function->getId(), + $function->setAttribute('scheduleNext', (int) $next) + )); - ResqueScheduler::enqueueAt($next, Event::FUNCTIONS_QUEUE_NAME, Event::FUNCTIONS_CLASS_NAME, [ - 'projectId' => $projectId, - 'webhooks' => $webhooks, - 'functionId' => $function->getId(), - 'userId' => $userId, - 'executionId' => null, - 'trigger' => 'schedule', - 'scheduleOriginal' => $function->getAttribute('schedule', ''), - ]); // Async task reschedule - - $this->execute( - projectId: $projectId, - function: $function, - dbForProject: $database, - executionId: $executionId, - 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.')'); + if ($function === false) { + throw new Exception('Function update failed.'); } + $reschedule = new Func(); + $reschedule + ->setFunction($function) + ->setType('schedule') + ->setUser($user) + ->setProject($project); + + // Async task reschedule + $reschedule->schedule($next); + $this->execute( - projectId: $projectId, + project: $project, function: $function, dbForProject: $database, - executionId: $executionId, - webhooks: $webhooks, - trigger: $trigger, - event: $event, - eventData: $eventData, - data: $data, - userId: $userId, - jwt: $jwt + trigger: 'schedule' ); break; @@ -194,24 +180,23 @@ class FunctionsV1 extends Worker } private function execute( - string $projectId, + Document $project, 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, + ?Document $user = null, + string $jwt = null ) { $functionId = $function->getId(); $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 +207,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,20 +218,21 @@ 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, $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, @@ -259,13 +245,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; }); @@ -280,8 +267,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); @@ -289,7 +276,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, @@ -301,44 +288,62 @@ class FunctionsV1 extends Worker ); /** Update execution status */ - $execution->setAttribute('status', $executionResponse['status']); - $execution->setAttribute('statusCode', $executionResponse['statusCode']); - $execution->setAttribute('response', $executionResponse['response']); - $execution->setAttribute('stderr', $executionResponse['stderr']); - $execution->setAttribute('time', $executionResponse['time']); + $execution + ->setAttribute('status', $executionResponse['status']) + ->setAttribute('statusCode', $executionResponse['statusCode']) + ->setAttribute('response', $executionResponse['response']) + ->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)); + /** @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('webhooks', $webhooks) - ->setParam('event', 'functions.executions.update') - ->setParam('eventData', $execution->getArrayCopy(array_keys($executionModel->getRules()))); - $executionUpdate->trigger(); + ->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( + // Pass first, most verbose event pattern + event: $allEvents[0], + payload: $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'] ); @@ -349,7 +354,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', '')) @@ -357,7 +362,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 6905593a8..8c78ff900 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,19 +31,23 @@ class MailsV1 extends Worker return; } + $project = new Document($this->args['project']); + $user = new Document($this->args['user'] ?? []); + $team = new Document($this->args['team'] ?? []); + $recipient = $this->args['recipient']; - $name = $this->args['name']; $url = $this->args['url']; - $project = $this->args['project']; + $name = $this->args['name']; $type = $this->args['type']; $prefix = $this->getPrefix($type); $locale = new Locale($this->args['locale']); + $projectName = $project->getAttribute('name', '[APP-NAME]'); if (!$this->doesLocaleExist($locale, $prefix)) { $locale->setDefault('en'); } - $from = $this->args['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) { @@ -57,9 +62,9 @@ class MailsV1 extends Worker $body->setParam('{{attempt}}', $attempt); break; 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"), $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: @@ -79,7 +84,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 861347221..e44881c1b 100644 --- a/app/workers/webhooks.php +++ b/app/workers/webhooks.php @@ -3,15 +3,19 @@ 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 { + protected array $errors = []; + + public function getName(): string + { return "webhooks"; } @@ -21,77 +25,72 @@ class WebhooksV1 extends Worker public function run(): void { - $errors = []; + $events = $this->args['events']; + $payload = json_encode($this->args['payload']); + $project = new Document($this->args['project']); + $user = new Document($this->args['user'] ?? []); - // Event - $projectId = $this->args['projectId'] ?? ''; - $webhooks = $this->args['webhooks'] ?? []; - $userId = $this->args['userId'] ?? ''; - $event = $this->args['event'] ?? ''; - $eventData = \json_encode($this->args['eventData']); - - foreach ($webhooks as $webhook) { - if (!(isset($webhook['events']) && \is_array($webhook['events']) && \in_array($event, $webhook['events']))) { - continue; + foreach ($project->getAttribute('webhooks', []) as $webhook) { + if (array_intersect($webhook->getAttribute('events', []), $events)) { + $this->execute($events, $payload, $webhook, $user, $project); } - - $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($url); - - \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 (!empty($errors)) { - throw new Exception(\implode(" / \n\n", $errors)); + 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'); + + $ch = \curl_init($webhook->getAttribute('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-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(), + 'X-' . APP_NAME . '-Webhook-Signature: ' . $webhook->getAttribute('signature', 'not-yet-implemented'), + ] + ); + + 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)) { + $this->errors[] = \curl_error($ch) . ' in events ' . implode(', ', $events) . ' for webhook ' . $webhook->getAttribute('name'); + } + + \curl_close($ch); + } + public function shutdown(): void { + $this->errors = []; } } diff --git a/composer.json b/composer.json index 75bab2aed..71a810ac7 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 c2b51c52d..751b5bbf2 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", @@ -6576,5 +6576,5 @@ "platform-overrides": { "php": "8.0" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.1.0" } diff --git a/phpunit.xml b/phpunit.xml index 2fbb75e22..9f5e42b84 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 diff --git a/public/dist/scripts/app-all.js b/public/dist/scripts/app-all.js index 10fd74576..a973062ef 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 7240e243c..454c8586e 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);} diff --git a/src/Appwrite/Event/Audit.php b/src/Appwrite/Event/Audit.php new file mode 100644 index 000000000..b2d34424e --- /dev/null +++ b/src/Appwrite/Event/Audit.php @@ -0,0 +1,130 @@ +resource = $resource; + + 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; + + 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; + + 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 $ip + * @return self + */ + public function setIP(string $ip): self + { + $this->ip = $ip; + + 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, + '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/Build.php b/src/Appwrite/Event/Build.php new file mode 100644 index 000000000..970de8265 --- /dev/null +++ b/src/Appwrite/Event/Build.php @@ -0,0 +1,103 @@ +resource = $resource; + + return $this; + } + + /** + * Returns set resource document for the build event. + * + * @return null|Document + */ + public function getResource(): ?Document + { + return $this->resource; + } + + /** + * Sets deployment for the build event. + * + * @param Document $deployment + * @return self + */ + public function setDeployment(Document $deployment): self + { + $this->deployment = $deployment; + + return $this; + } + + /** + * Returns set deployment for the build event. + * + * @return null|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/Certificate.php b/src/Appwrite/Event/Certificate.php new file mode 100644 index 000000000..894c09496 --- /dev/null +++ b/src/Appwrite/Event/Certificate.php @@ -0,0 +1,79 @@ +domain = $domain; + + return $this; + } + + /** + * Returns the set domain for this certificate event. + * + * @return null|Document + */ + public function getDomain(): ?Document + { + return $this->domain; + } + + /** + * Set if the certificate needs to be validated. + * + * @param bool $skipRenewCheck + * @return self + */ + public function setSkipRenewCheck(bool $skipRenewCheck): self + { + $this->skipRenewCheck = $skipRenewCheck; + + return $this; + } + + /** + * Return if the certificate needs be validated. + * + * @return bool + */ + public function getSkipRenewCheck(): bool + { + return $this->skipRenewCheck; + } + + /** + * 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, [ + 'project' => $this->project, + 'domain' => $this->domain, + 'validateTarget' => $this->validateTarget, + 'validateCNAME' => $this->validateCNAME + ]); + } +} diff --git a/src/Appwrite/Event/Database.php b/src/Appwrite/Event/Database.php new file mode 100644 index 000000000..5e5e68785 --- /dev/null +++ b/src/Appwrite/Event/Database.php @@ -0,0 +1,103 @@ +type = $type; + + 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 Document $collection + * @return self + */ + public function setCollection(Document $collection): self + { + $this->collection = $collection; + + return $this; + } + + /** + * Returns set collection for this event. + * + * @return null|Document + */ + public function getCollection(): ?Document + { + return $this->collection; + } + + /** + * Set the document for this database event. + * + * @param Document $document + * @return self + */ + public function setDocument(Document $document): self + { + $this->document = $document; + + return $this; + } + + /** + * Returns set document for this database event. + * @return null|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, [ + '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 000000000..1c2be4b01 --- /dev/null +++ b/src/Appwrite/Event/Delete.php @@ -0,0 +1,123 @@ +type = $type; + + return $this; + } + + /** + * Returns the set type for the delete event. + * + * @return string + */ + public function getType(): string + { + return $this->type; + } + + /** + * Set timestamp. + * + * @param int $timestamp + * @return self + */ + public function setTimestamp(int $timestamp): self + { + $this->timestamp = $timestamp; + + return $this; + } + + /** + * Set timestamp for 1 day interval. + * + * @param int $timestamp + * @return self + */ + public function setTimestamp1d(int $timestamp): self + { + $this->timestamp1d = $timestamp; + + return $this; + } + + /** + * Sets timestamp for 30m interval. + * + * @param int $timestamp + * @return self + */ + public function setTimestamp30m(int $timestamp): self + { + $this->timestamp30m = $timestamp; + + return $this; + } + + /** + * Sets the document for the delete event. + * + * @param Document $document + * @return self + */ + public function setDocument(Document $document): self + { + $this->document = $document; + + return $this; + } + + /** + * Returns the set document for the delete event. + * + * @return null|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, [ + 'project' => $this->project, + 'type' => $this->type, + 'document' => $this->document, + 'timestamp' => $this->timestamp, + 'timestamp1d' => $this->timestamp1d, + 'timestamp30m' => $this->timestamp30m + ]); + } +} diff --git a/src/Appwrite/Event/Event.php b/src/Appwrite/Event/Event.php index 4b15f5d04..871c4c4f4 100644 --- a/src/Appwrite/Event/Event.php +++ b/src/Appwrite/Event/Event.php @@ -2,12 +2,13 @@ namespace Appwrite\Event; +use InvalidArgumentException; use Resque; +use Utopia\Database\Document; 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'; @@ -30,27 +31,20 @@ class Event const BUILDS_QUEUE_NAME = 'v1-builds'; const BUILDS_CLASS_NAME = 'BuildsV1'; - - /** - * @var string - */ - protected $queue = ''; + + 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 $context = null; /** - * @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) { @@ -59,48 +53,172 @@ class Event } /** + * Set queue used for this event. + * * @param string $queue - * return $this + * @return Event */ public function setQueue(string $queue): self { $this->queue = $queue; + return $this; } /** + * Get queue used for this event. + * * @return string */ - public function getQueue() + public function getQueue(): string { return $this->queue; } /** - * @param string $class - * return $this + * Set event name used for this event. + * @param string $event + * @return Event */ - public function setClass(string $class): self + public function setEvent(string $event): self { - $this->class = $class; + $this->event = $event; + return $this; } /** + * Get event name used for this event. + * * @return string */ - public function getClass() + public function getEvent(): string + { + return $this->event; + } + + /** + * Set project for this event. + * + * @param Document $project + * @return self + */ + public function setProject(Document $project): self + { + $this->project = $project; + + 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; + + 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; + + 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; + + return $this; + } + + /** + * Get context for this event. + * + * @return Document + */ + public function getContext(): ?Document + { + return $this->context; + } + + /** + * Set class used for this event. + * @param string $class + * @return self + */ + public function setClass(string $class): self + { + $this->class = $class; + + return $this; + } + + /** + * Get class used for this event. + * + * @return string + */ + 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 self */ - public function setParam(string $key, $value): self + public function setParam(string $key, mixed $value): self { $this->params[$key] = $value; @@ -108,29 +226,186 @@ 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; + } + + /** + * Get all params of the event. + * + * @return array + */ + public function getParams(): array + { + return $this->params; } /** * Execute Event. + * + * @return string|bool + * @throws InvalidArgumentException */ - public function trigger(): void + public function trigger(): string|bool { - Resque::enqueue($this->queue, $this->class, $this->params); - - $this->reset(); + return Resque::enqueue($this->queue, $this->class, [ + 'project' => $this->project, + 'user' => $this->user, + 'payload' => $this->payload, + 'context' => $this->context, + 'events' => Event::generateEvents($this->getEvent(), $this->getParams()) + ]); } + /** + * Resets event. + * + * @return self + */ public function reset(): self { $this->params = []; 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); + $count = \count($parts); + + /** + * Identify all sections of the pattern. + */ + $type = $parts[0] ?? false; + $resource = $parts[1] ?? false; + $hasSubResource = $count > 3 && \str_starts_with($parts[3], '['); + + 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 + }; + + return [ + 'type' => $type, + 'resource' => $resource, + 'subType' => $subType, + 'subResource' => $subResource, + 'action' => $action, + 'attribute' => $attribute, + ]; + } + + /** + * 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)); + $paramKeys = \array_keys($params); + $paramValues = \array_values($params); + + $patterns = []; + + $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."); + } + + 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 { + $patterns[] = \implode('.', [$type, $resource, $action]); + } + if ($attribute) { + $patterns[] = \implode('.', [$type, $resource, $action, $attribute]); + } + } + if ($subResource) { + $patterns[] = \implode('.', [$type, $resource, $subType, $subResource]); + } + $patterns[] = \implode('.', [$type, $resource]); + + /** + * 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); + $events = \array_unique($events); + + return $events; + } } diff --git a/src/Appwrite/Event/Func.php b/src/Appwrite/Event/Func.php new file mode 100644 index 000000000..98652431f --- /dev/null +++ b/src/Appwrite/Event/Func.php @@ -0,0 +1,178 @@ +function = $function; + + return $this; + } + + /** + * Returns set function document for the function event. + * + * @return null|Document + */ + public function getFunction(): ?Document + { + return $this->function; + } + + /** + * Sets execution for the function event. + * + * @param Document $execution + * @return self + */ + public function setExecution(Document $execution): self + { + $this->execution = $execution; + + return $this; + } + + /** + * Returns set execution for the function event. + * + * @return null|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; + + 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; + + 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; + + 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, [ + 'project' => $this->project, + 'user' => $this->user, + 'function' => $this->function, + 'execution' => $this->execution, + 'type' => $this->type, + 'jwt' => $this->jwt, + 'payload' => $this->payload, + 'data' => $this->data + ]); + } + + /** + * 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, [ + 'project' => $this->project, + 'user' => $this->user, + 'function' => $this->function, + 'execution' => $this->execution, + 'type' => $this->type, + 'payload' => $this->payload, + 'data' => $this->data + ]); + } +} diff --git a/src/Appwrite/Event/Mail.php b/src/Appwrite/Event/Mail.php new file mode 100644 index 000000000..b3325c00b --- /dev/null +++ b/src/Appwrite/Event/Mail.php @@ -0,0 +1,181 @@ +team = $team; + + return $this; + } + + /** + * Returns set team for the mail event. + * + * @return null|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; + + 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; + + 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; + + 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; + + 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; + + 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, + '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()) + ]); + } +} diff --git a/src/Appwrite/Event/Validator/Event.php b/src/Appwrite/Event/Validator/Event.php new file mode 100644 index 000000000..b68cdfaa0 --- /dev/null +++ b/src/Appwrite/Event/Validator/Event.php @@ -0,0 +1,127 @@ + 6) { + return false; + } + + /** + * Identify all sections of the pattern. + */ + $type = $parts[0] ?? false; + $resource = $parts[1] ?? false; + $hasSubResource = $count > 3 && ($events[$type]['$resource'] ?? false) && ($events[$type][$parts[2]]['$resource'] ?? false); + + 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 (!\array_key_exists($type, $events)) { + return false; + } + + if ($subType) { + if ($action && !\array_key_exists($action, $events[$type][$subType])) { + return false; + } + if (!($subResource) || !\array_key_exists($subType, $events[$type])) { + return false; + } + } else { + if ($action && !\array_key_exists($action, $events[$type])) { + return false; + } + } + + if ($attribute) { + if (($subType)) { + if (!\array_key_exists($attribute, $events[$type][$subType][$action])) { + return false; + } + } else { + if (!\array_key_exists($attribute, $events[$type][$action])) { + 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/src/Appwrite/Messaging/Adapter.php b/src/Appwrite/Messaging/Adapter.php index 6ef2d5cfd..191084724 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 d7d138a79..ad38444cb 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,16 +118,16 @@ 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, 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 @@ -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 @@ -246,77 +246,73 @@ class Realtime extends Adapter $roles = []; $permissionsChanged = false; $projectId = null; + // TODO: add method here to remove all the magic index accesses + $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') === 'bucket') ? $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/src/Appwrite/Migration/Migration.php b/src/Appwrite/Migration/Migration.php index 723f210b8..f66936061 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', ]; /** diff --git a/src/Appwrite/Migration/Version/V13.php b/src/Appwrite/Migration/Version/V13.php new file mode 100644 index 000000000..4f1003af5 --- /dev/null +++ b/src/Appwrite/Migration/Version/V13.php @@ -0,0 +1,215 @@ +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/e2e/Scopes/ProjectCustom.php b/tests/e2e/Scopes/ProjectCustom.php index 07b5d3be5..049583743 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 3aa368baa..301ffff8a 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/e2e/Services/Functions/FunctionsConsoleClientTest.php b/tests/e2e/Services/Functions/FunctionsConsoleClientTest.php index a0f6cca5e..0180a87de 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 a89599565..892730281 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 3431e13ca..a766b269e 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]['response']); // 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]['response']); // 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]['response']); // 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]['response']); // 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]['response']); // 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/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index 5d1b0937f..e0a7cb158 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 0b19665bf..9d75bc159 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 c5155a97a..017792a61 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('account.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('account.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,23 +345,27 @@ 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->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']); /** * 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, - '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('account.verification.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); @@ -370,11 +393,11 @@ 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, - '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('account.verification.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('account.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('account.sessions.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,13 +513,22 @@ 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->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']); /** * 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 +536,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 +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('account.recovery.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('account.recovery.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('database.documents.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('database.documents.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('database.documents.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('database.documents.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('database.documents.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('database.documents.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('storage.files.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('storage.files.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('storage.files.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); - - $deployment = $this->client->call(Client::METHOD_POST, '/functions/'.$functionId.'/deployments', array_merge([ + $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'], '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.executions.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.executions.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.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.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.memberships.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 b8ed3b770..fd65b9484 100644 --- a/tests/e2e/Services/Webhooks/WebhooksBase.php +++ b/tests/e2e/Services/Webhooks/WebhooksBase.php @@ -23,7 +23,9 @@ trait WebhooksBase 'write' => ['role:all'], '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-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']); @@ -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-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']); $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-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']); @@ -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-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']); @@ -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-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']); @@ -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-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']); @@ -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-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']); @@ -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-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']); @@ -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-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']); @@ -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-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']); @@ -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-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']); @@ -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-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']); @@ -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-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']); @@ -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-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']); @@ -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-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']); @@ -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-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']); @@ -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-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']); @@ -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 355c8f585..47eebe4b7 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-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']); @@ -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-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']); @@ -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-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']); @@ -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-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']); @@ -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-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']); @@ -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-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']); @@ -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-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']); @@ -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-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']); @@ -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-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']); @@ -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-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']); - $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-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']); @@ -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-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']); @@ -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-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']); @@ -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-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']); @@ -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 b79ae37d7..7c815595f 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-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']); @@ -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-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']); @@ -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-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']); @@ -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-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']); @@ -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-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']); @@ -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-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']); @@ -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-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']); @@ -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-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']); @@ -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-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']); @@ -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-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']); 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-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']); @@ -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-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']); @@ -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-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']); - // 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-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']); @@ -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-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']); @@ -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-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']); diff --git a/tests/e2e/Services/Workers/WebhooksTest.php b/tests/e2e/Services/Workers/WebhooksTest.php deleted file mode 100644 index f859f9885..000000000 --- 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 58d7a1c04..d653541ad 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,13 +59,12 @@ class EventTest extends TestCase { $this->object ->setParam('eventKey1', 'eventValue1') - ->setParam('eventKey2', 'eventValue2') - ; + ->setParam('eventKey2', 'eventValue2'); $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); } @@ -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,60 @@ 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.[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' + ]); + $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(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', [ + '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'); + } + } } diff --git a/tests/unit/Event/Validator/EventValidatorTest.php b/tests/unit/Event/Validator/EventValidatorTest.php new file mode 100644 index 000000000..e52399959 --- /dev/null +++ b/tests/unit/Event/Validator/EventValidatorTest.php @@ -0,0 +1,63 @@ +object = new Event(); + } + + public function tearDown(): void + { + } + + 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')); + $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->assertTrue($this->object->isValid('teams.*.memberships.*.update.status')); + + /** + * 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')); + $this->assertFalse($this->object->isValid('teams.*.memberships.*.update.unknown')); + } +} diff --git a/tests/unit/Messaging/MessagingTest.php b/tests/unit/Messaging/MessagingTest.php index a0cad0bae..0d7dd2a2e 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']); + } } diff --git a/tests/unit/Migration/MigrationV13Test.php b/tests/unit/Migration/MigrationV13Test.php new file mode 100644 index 000000000..3d0b0e297 --- /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(44, $events); + } +}