1
0
Fork 0
mirror of synced 2024-06-13 16:24:47 +12:00

Merge branch '0.14.x' of https://github.com/appwrite/appwrite into feat-use-build-timeout

This commit is contained in:
Torsten Dittmann 2022-05-12 19:57:20 +02:00
commit 6c9e17ec74
60 changed files with 4720 additions and 2461 deletions

View file

@ -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 的本机主机上完成安装后,服务器可能需要几分钟才能启动。

View file

@ -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.

View file

@ -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.',
]
]
];

File diff suppressed because it is too large Load diff

View file

@ -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();

View file

@ -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);
}
@ -969,7 +1006,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)
@ -999,7 +1036,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);
@ -1049,7 +1086,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);
@ -1072,7 +1109,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')
@ -1085,10 +1122,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);
@ -1101,7 +1140,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);
@ -1111,13 +1150,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();
});
});

View file

@ -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();
});

View file

@ -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();

View file

@ -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();

View file

@ -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

View file

@ -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);

View file

@ -1,19 +1,16 @@
<?php
use Appwrite\Auth\Auth;
use Appwrite\Event\Event;
use Appwrite\Messaging\Adapter\Realtime;
use Utopia\App;
use Appwrite\Extend\Exception;
use Utopia\Abuse\Abuse;
use Utopia\Abuse\Adapters\TimeLimit;
use Utopia\Database\Document;
use Utopia\Storage\Device\DOSpaces;
use Utopia\Database\Validator\Authorization;
use Utopia\Storage\Device\Local;
use Utopia\Storage\Device\S3;
use Utopia\Storage\Storage;
App::init(function ($utopia, $request, $response, $project, $user, $events, $audits, $usage, $deletes, $database, $dbForProject, $mode) {
App::init(function ($utopia, $request, $response, $project, $user, $events, $audits, $mails, $usage, $deletes, $database, $dbForProject, $mode) {
/** @var Utopia\App $utopia */
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
@ -21,7 +18,8 @@ App::init(function ($utopia, $request, $response, $project, $user, $events, $aud
/** @var Utopia\Database\Document $user */
/** @var Utopia\Registry\Registry $register */
/** @var Appwrite\Event\Event $events */
/** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Event\Audit $audits */
/** @var Appwrite\Event\Mail $mails */
/** @var Appwrite\Stats\Stats $usage */
/** @var Appwrite\Event\Event $deletes */
/** @var Appwrite\Event\Event $database */
@ -88,27 +86,23 @@ App::init(function ($utopia, $request, $response, $project, $user, $events, $aud
* Background Jobs
*/
$events
->setParam('projectId', $project->getId())
->setParam('webhooks', $project->getAttribute('webhooks', []))
->setParam('userId', $user->getId())
->setParam('event', $route->getLabel('event', ''))
->setParam('eventData', [])
->setParam('functionId', null)
->setParam('executionId', null)
->setParam('trigger', 'event')
->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');
}, ['utopia', 'request', 'response', 'project', 'events', 'audits', 'usage', 'deletes', 'database', 'mode', 'dbForProject'], 'api');

View file

@ -165,7 +165,7 @@ App::get('/console/webhooks')
$page
->setParam('events', Config::getParam('events', []))
;
$layout
->setParam('title', APP_NAME.' - Webhooks')
->setParam('body', $page);

View file

@ -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']);

View file

@ -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':

View file

@ -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);
});
});

View file

@ -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');
}

View file

@ -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
]);
});
(new Certificate())
->setDomain(new Document([
'domain' => $domain
]))
->setSkipRenewCheck(true)
->trigger();
});

View file

@ -1,17 +1,20 @@
<?php
use Appwrite\Event\Event;
use Appwrite\Resque\Worker;
use Utopia\Audit\Audit;
use Utopia\CLI\Console;
use Utopia\Database\Document;
require_once __DIR__.'/../init.php';
require_once __DIR__ . '/../init.php';
Console::title('Audits V1 Worker');
Console::success(APP_NAME . ' audits worker v1 has started');
class AuditsV1 extends Worker
{
public function getName(): string {
public function getName(): string
{
return "audits";
}
@ -21,30 +24,39 @@ class AuditsV1 extends Worker
public function run(): void
{
$projectId = $this->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
}
}

View file

@ -1,7 +1,9 @@
<?php
use Appwrite\Event\Event;
use Appwrite\Messaging\Adapter\Realtime;
use Appwrite\Resque\Worker;
use Appwrite\Utopia\Response\Model\Deployment;
use Cron\CronExpression;
use Executor\Executor;
use Utopia\Database\Validator\Authorization;
@ -11,43 +13,41 @@ use Utopia\Storage\Storage;
use Utopia\Database\Document;
use Utopia\Config\Config;
require_once __DIR__.'/../init.php';
require_once __DIR__ . '/../init.php';
// Disable Auth since we already validate it in the API
Authorization::disable();
Console::title('Builds V1 Worker');
Console::success(APP_NAME.' build worker v1 has started');
Console::success(APP_NAME . ' build worker v1 has started');
// TODO: Executor should return appropriate response codes.
class BuildsV1 extends Worker
{
/**
* @var Executor
*/
private $executor = null;
{
private ?Executor $executor = null;
public function getName(): string
public function getName(): string
{
return "builds";
}
public function init(): void {
public function init(): void
{
$this->executor = new Executor(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
{
}
}

View file

@ -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'));
}
}
}
}
}

View file

@ -1,5 +1,6 @@
<?php
use Appwrite\Event\Event;
use Appwrite\Messaging\Adapter\Realtime;
use Appwrite\Resque\Worker;
use Utopia\CLI\Console;
@ -20,12 +21,11 @@ class DatabaseV1 extends Worker
public function run(): void
{
Authorization::disable();
$projectId = $this->args['projectId'] ?? '';
$type = $this->args['type'] ?? '';
$collection = $this->args['collection'] ?? [];
$collection = new Document($collection);
$document = $this->args['document'] ?? [];
$document = new Document($document);
$type = $this->args['type'];
$project = new Document($this->args['project']);
$collection = new Document($this->args['collection'] ?? []);
$document = new Document($this->args['document'] ?? []);
if($collection->isEmpty()) {
throw new Exception('Missing collection');
@ -37,16 +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());
}
}

View file

@ -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

View file

@ -1,13 +1,13 @@
<?php
use Appwrite\Event\Event;
use Appwrite\Event\Func;
use Appwrite\Messaging\Adapter\Realtime;
use Appwrite\Resque\Worker;
use Appwrite\Stats\Stats;
use Appwrite\Utopia\Response\Model\Execution;
use Cron\CronExpression;
use Executor\Executor;
use Swoole\Runtime;
use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Config\Config;
@ -15,23 +15,19 @@ use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
require_once __DIR__.'/../init.php';
require_once __DIR__ . '/../init.php';
Console::title('Functions V1 Worker');
Console::success(APP_NAME . ' functions worker v1 has started');
class FunctionsV1 extends Worker
{
/**
* @var Executor
*/
private $executor = null;
private ?Executor $executor = null;
public array $args = [];
public array $allowed = [];
public function getName(): string {
public function getName(): string
{
return "functions";
}
@ -42,65 +38,87 @@ class FunctionsV1 extends Worker
public function run(): void
{
$projectId = $this->args['projectId'] ?? '';
$functionId = $this->args['functionId'] ?? '';
$webhooks = $this->args['webhooks'] ?? [];
$executionId = $this->args['executionId'] ?? '';
$trigger = $this->args['trigger'] ?? '';
$event = $this->args['event'] ?? '';
$scheduleOriginal = $this->args['scheduleOriginal'] ?? '';
$eventData = (!empty($this->args['eventData'])) ? json_encode($this->args['eventData']) : '';
$data = $this->args['data'] ?? '';
$userId = $this->args['userId'] ?? '';
$jwt = $this->args['jwt'] ?? '';
$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,47 +288,65 @@ 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) {
$endtime = \microtime(true);
$time = $endtime - $execution->getAttribute('dateCreated');
$execution->setAttribute('time', $time);
$execution->setAttribute('status', 'failed');
$execution->setAttribute('statusCode', $th->getCode());
$execution->setAttribute('stderr', $th->getMessage());
$execution
->setAttribute('time', $time)
->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']
);
@ -352,7 +357,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', ''))
@ -360,7 +365,6 @@ class FunctionsV1 extends Worker
->setParam('networkRequestSize', 0)
->setParam('networkResponseSize', 0)
->submit();
$usage->submit();
}
}

View file

@ -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')

View file

@ -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 = [];
}
}

View file

@ -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.*",

16
composer.lock generated
View file

@ -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"
}

View file

@ -29,7 +29,6 @@
<directory>./tests/e2e/Services/Storage</directory>
<directory>./tests/e2e/Services/Teams</directory>
<directory>./tests/e2e/Services/Users</directory>
<directory>./tests/e2e/Services/Workers</directory>
<directory>./tests/e2e/Services/Webhooks</directory>
<file>./tests/e2e/Services/Functions/FunctionsBase.php</file>
<file>./tests/e2e/Services/Functions/FunctionsCustomServerTest.php</file>

View file

@ -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<events.length;y++){if(events[y]==="init"){element.addEventListener("rendered",function(){apply(form.toJson(element));},{once:true});}else{}
element.setAttribute("data-event","none");}
break;default:break;}}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-forms-headers",controller:function(element){let key=document.createElement("input");let value=document.createElement("input");let wrap=document.createElement("div");let cell1=document.createElement("div");let cell2=document.createElement("div");key.type="text";key.className="margin-bottom-no";key.placeholder="Key";value.type="text";value.className="margin-bottom-no";value.placeholder="Value";wrap.className="row thin margin-bottom-small";cell1.className="col span-6";cell2.className="col span-6";element.parentNode.insertBefore(wrap,element);cell1.appendChild(key);cell2.appendChild(value);wrap.appendChild(cell1);wrap.appendChild(cell2);key.addEventListener("input",function(){syncA();});value.addEventListener("input",function(){syncA();});element.addEventListener("change",function(){syncB();});let syncA=function(){element.value=key.value.toLowerCase()+":"+value.value.toLowerCase();};let syncB=function(){let split=element.value.toLowerCase().split(":");key.value=split[0]||"";value.value=split[1]||"";key.value=key.value.trim();value.value=value.value.trim();};syncB();}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-forms-key-value",controller:function(element){let key=document.createElement("input");let value=document.createElement("input");let wrap=document.createElement("div");let cell1=document.createElement("div");let cell2=document.createElement("div");key.type="text";key.className="margin-bottom-no";key.placeholder="Key";key.required=true;value.type="text";value.className="margin-bottom-no";value.placeholder="Value";value.required=true;wrap.className="row thin margin-bottom-small";cell1.className="col span-6";cell2.className="col span-6";element.parentNode.insertBefore(wrap,element);cell1.appendChild(key);cell2.appendChild(value);wrap.appendChild(cell1);wrap.appendChild(cell2);key.addEventListener("input",function(){syncA();});value.addEventListener("input",function(){syncA();});element.addEventListener("change",function(){syncB();});let syncA=function(){element.name=key.value;element.value=value.value;};let syncB=function(){key.value=element.name||"";value.value=element.value||"";};syncB();}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-move-down",controller:function(element){Array.prototype.slice.call(element.querySelectorAll("[data-move-down]")).map(function(obj){obj.addEventListener("click",function(){if(element.nextElementSibling){console.log('down',element.offsetHeight);element.parentNode.insertBefore(element.nextElementSibling,element);element.scrollIntoView({block:'center'});}});});}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-move-up",controller:function(element){Array.prototype.slice.call(element.querySelectorAll("[data-move-up]")).map(function(obj){obj.addEventListener("click",function(){if(element.previousElementSibling){console.log('up',element);element.parentNode.insertBefore(element,element.previousElementSibling);element.scrollIntoView({block:'center'});}});});}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-nav",repeat:false,controller:function(element,view,container,document){let titles=document.querySelectorAll('[data-forms-nav-anchor]');let links=element.querySelectorAll('[data-forms-nav-link]');let minLink=null;let check=function(){let minDistance=null;let minElement=null;for(let i=0;i<titles.length;++i){let title=titles[i];let distance=title.getBoundingClientRect().top;console.log(i);if((minDistance===null||minDistance>=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);}

View file

@ -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<events.length;y++){if(events[y]==="init"){element.addEventListener("rendered",function(){apply(form.toJson(element));},{once:true});}else{}
element.setAttribute("data-event","none");}
break;default:break;}}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-forms-headers",controller:function(element){let key=document.createElement("input");let value=document.createElement("input");let wrap=document.createElement("div");let cell1=document.createElement("div");let cell2=document.createElement("div");key.type="text";key.className="margin-bottom-no";key.placeholder="Key";value.type="text";value.className="margin-bottom-no";value.placeholder="Value";wrap.className="row thin margin-bottom-small";cell1.className="col span-6";cell2.className="col span-6";element.parentNode.insertBefore(wrap,element);cell1.appendChild(key);cell2.appendChild(value);wrap.appendChild(cell1);wrap.appendChild(cell2);key.addEventListener("input",function(){syncA();});value.addEventListener("input",function(){syncA();});element.addEventListener("change",function(){syncB();});let syncA=function(){element.value=key.value.toLowerCase()+":"+value.value.toLowerCase();};let syncB=function(){let split=element.value.toLowerCase().split(":");key.value=split[0]||"";value.value=split[1]||"";key.value=key.value.trim();value.value=value.value.trim();};syncB();}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-forms-key-value",controller:function(element){let key=document.createElement("input");let value=document.createElement("input");let wrap=document.createElement("div");let cell1=document.createElement("div");let cell2=document.createElement("div");key.type="text";key.className="margin-bottom-no";key.placeholder="Key";key.required=true;value.type="text";value.className="margin-bottom-no";value.placeholder="Value";value.required=true;wrap.className="row thin margin-bottom-small";cell1.className="col span-6";cell2.className="col span-6";element.parentNode.insertBefore(wrap,element);cell1.appendChild(key);cell2.appendChild(value);wrap.appendChild(cell1);wrap.appendChild(cell2);key.addEventListener("input",function(){syncA();});value.addEventListener("input",function(){syncA();});element.addEventListener("change",function(){syncB();});let syncA=function(){element.name=key.value;element.value=value.value;};let syncB=function(){key.value=element.name||"";value.value=element.value||"";};syncB();}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-move-down",controller:function(element){Array.prototype.slice.call(element.querySelectorAll("[data-move-down]")).map(function(obj){obj.addEventListener("click",function(){if(element.nextElementSibling){console.log('down',element.offsetHeight);element.parentNode.insertBefore(element.nextElementSibling,element);element.scrollIntoView({block:'center'});}});});}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-move-up",controller:function(element){Array.prototype.slice.call(element.querySelectorAll("[data-move-up]")).map(function(obj){obj.addEventListener("click",function(){if(element.previousElementSibling){console.log('up',element);element.parentNode.insertBefore(element,element.previousElementSibling);element.scrollIntoView({block:'center'});}});});}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-nav",repeat:false,controller:function(element,view,container,document){let titles=document.querySelectorAll('[data-forms-nav-anchor]');let links=element.querySelectorAll('[data-forms-nav-link]');let minLink=null;let check=function(){let minDistance=null;let minElement=null;for(let i=0;i<titles.length;++i){let title=titles[i];let distance=title.getBoundingClientRect().top;console.log(i);if((minDistance===null||minDistance>=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);}

View file

@ -0,0 +1,130 @@
<?php
namespace Appwrite\Event;
use Resque;
class Audit extends Event
{
protected string $resource = '';
protected string $mode = '';
protected string $userAgent = '';
protected string $ip = '';
public function __construct()
{
parent::__construct(Event::AUDITS_QUEUE_NAME, Event::AUDITS_CLASS_NAME);
}
/**
* Set resource for this audit event.
*
* @param string $resource
* @return self
*/
public function setResource(string $resource): self
{
$this->resource = $resource;
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())
]);
}
}

View file

@ -0,0 +1,103 @@
<?php
namespace Appwrite\Event;
use Resque;
use Utopia\Database\Document;
class Build extends Event
{
protected string $type = '';
protected ?Document $resource = null;
protected ?Document $deployment = null;
public function __construct()
{
parent::__construct(Event::BUILDS_QUEUE_NAME, Event::BUILDS_CLASS_NAME);
}
/**
* Sets resource document for the build event.
*
* @param Document $resource
* @return self
*/
public function setResource(Document $resource): self
{
$this->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
]);
}
}

View file

@ -0,0 +1,79 @@
<?php
namespace Appwrite\Event;
use Resque;
use Utopia\Database\Document;
class Certificate extends Event
{
protected bool $skipRenewCheck = false;
protected ?Document $domain = null;
public function __construct()
{
parent::__construct(Event::CERTIFICATES_QUEUE_NAME, Event::CERTIFICATES_CLASS_NAME);
}
/**
* Set domain for this certificates event.
*
* @param Document $domain
* @return self
*/
public function setDomain(Document $domain): self
{
$this->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
]);
}
}

View file

@ -0,0 +1,103 @@
<?php
namespace Appwrite\Event;
use Resque;
use Utopia\Database\Document;
class Database extends Event
{
protected string $type = '';
protected ?Document $collection = null;
protected ?Document $document = null;
public function __construct()
{
parent::__construct(Event::DATABASE_QUEUE_NAME, Event::DATABASE_CLASS_NAME);
}
/**
* Sets the type for this database event (use the constants starting with DATABASE_TYPE_*).
*
* @param string $type
* @return self
*/
public function setType(string $type): self
{
$this->type = $type;
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())
]);
}
}

View file

@ -0,0 +1,123 @@
<?php
namespace Appwrite\Event;
use Resque;
use Utopia\Database\Document;
class Delete extends Event
{
protected string $type = '';
protected ?int $timestamp = null;
protected ?int $timestamp1d = null;
protected ?int $timestamp30m = null;
protected ?Document $document = null;
public function __construct()
{
parent::__construct(Event::DELETE_QUEUE_NAME, Event::DELETE_CLASS_NAME);
}
/**
* Sets the type for the delete event (use the constants starting with DELETE_TYPE_*).
*
* @param string $type
* @return self
*/
public function setType(string $type): self
{
$this->type = $type;
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
]);
}
}

View file

@ -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;
}
}

178
src/Appwrite/Event/Func.php Normal file
View file

@ -0,0 +1,178 @@
<?php
namespace Appwrite\Event;
use DateTime;
use Resque;
use ResqueScheduler;
use Utopia\Database\Document;
class Func extends Event
{
protected string $jwt = '';
protected string $type = '';
protected string $data = '';
protected ?Document $function = null;
protected ?Document $execution = null;
public function __construct()
{
parent::__construct(Event::FUNCTIONS_QUEUE_NAME, Event::FUNCTIONS_CLASS_NAME);
}
/**
* Sets function document for the function event.
*
* @param Document $function
* @return self
*/
public function setFunction(Document $function): self
{
$this->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
]);
}
}

181
src/Appwrite/Event/Mail.php Normal file
View file

@ -0,0 +1,181 @@
<?php
namespace Appwrite\Event;
use Resque;
use Utopia\Database\Document;
class Mail extends Event
{
protected string $recipient = '';
protected string $url = '';
protected string $type = '';
protected string $name = '';
protected string $locale = '';
protected ?Document $team = null;
public function __construct()
{
parent::__construct(Event::MAILS_QUEUE_NAME, Event::MAILS_CLASS_NAME);
}
/**
* Sets team for the mail event.
*
* @param Document $team
* @return self
*/
public function setTeam(Document $team): self
{
$this->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())
]);
}
}

View file

@ -0,0 +1,127 @@
<?php
namespace Appwrite\Event\Validator;
use Utopia\Config\Config;
use Utopia\Validator;
class Event extends Validator
{
/**
* Get Description.
*
* Returns validator description
*
* @return string
*/
public function getDescription(): string
{
return 'Event is not valid.';
}
/**
* Is valid.
*
* @param mixed $value
*
* @return bool
*/
public function isValid($value): bool
{
$events = Config::getParam('events', []);
$parts = \explode('.', $value);
$count = \count($parts);
if ($count < 2 || $count > 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;
}
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -40,6 +40,7 @@ abstract class Migration
'0.13.2' => 'V12',
'0.13.3' => 'V12',
'0.13.4' => 'V12',
'0.14.0' => 'V13',
];
/**

View file

@ -0,0 +1,215 @@
<?php
namespace Appwrite\Migration\Version;
use Appwrite\Event\Validator\Event;
use Appwrite\Migration\Migration;
use Utopia\CLI\Console;
use Utopia\Database\Document;
class V13 extends Migration
{
public array $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.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.email',
'users.update.name',
'users.update.password',
'users.update.status',
'users.sessions.delete',
'users.delete',
'teams.create',
'teams.update',
'teams.delete',
'teams.memberships.create',
'teams.memberships.update',
'teams.memberships.update.status',
'teams.memberships.delete'
];
public function execute(): void
{
Console::log('Migrating project: ' . $this->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)));
}
}

View file

@ -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,

View file

@ -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']);

View file

@ -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,

View file

@ -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,

View file

@ -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(10);
$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'], 'An internal curl error has occurred within the executor! Error Msg: Operation 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);
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -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();
}
}
}

View file

@ -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();
}
}
}

View file

@ -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 [];
}
}
}

View file

@ -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 [];
}
}
}

View file

@ -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']);

View file

@ -1,154 +0,0 @@
<?php
namespace Tests\E2E\Services\Workers;
use Tests\E2E\Client;
use Tests\E2E\Scopes\ProjectConsole;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\SideClient;
use Tests\E2E\Scopes\SideServer;
class WebhooksTest extends Scope
{
use ProjectConsole;
use SideClient;
public function testCreateProject(): array
{
/**
* Test for SUCCESS
*/
$team = $this->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']);
}
}

View file

@ -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');
}
}
}

View file

@ -0,0 +1,63 @@
<?php
namespace Appwrite\Tests;
use Appwrite\Event\Validator\Event;
use PHPUnit\Framework\TestCase;
use Utopia\Config\Config;
class EventValidatorTest extends TestCase
{
protected ?Event $object = null;
public function setUp(): void
{
Config::load('events', __DIR__.'/../../../../app/config/events.php');
$this->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'));
}
}

View file

@ -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']);
}
}

View file

@ -0,0 +1,51 @@
<?php
namespace Appwrite\Tests;
use Appwrite\Event\Validator\Event;
use ReflectionClass;
use Appwrite\Migration\Version\V13;
use Utopia\Database\Document;
class MigrationV13Test extends MigrationTest
{
public function setUp(): void
{
$this->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);
}
}