1
0
Fork 0
mirror of synced 2024-06-29 11:40:45 +12:00

Merge branch 'feat-database-indexing' into feat-storage-buckets

This commit is contained in:
Damodar Lohani 2021-12-19 12:26:16 +05:45
commit 99fd943f3c
44 changed files with 915 additions and 467 deletions

View file

@ -8,13 +8,13 @@
<br /> <br />
</p> </p>
[![Hacktoberfest](https://img.shields.io/static/v1?label=hacktoberfest&message=friendly&color=90a88b&style=flat-square)](https://hacktoberfest.appwrite.io) <!-- [![Hacktoberfest](https://img.shields.io/static/v1?label=hacktoberfest&message=friendly&color=90a88b&style=flat-square)](https://hacktoberfest.appwrite.io) -->
[![Discord](https://img.shields.io/discord/564160730845151244?label=discord&style=flat-square)](https://appwrite.io/discord?r=Github) [![Discord](https://img.shields.io/discord/564160730845151244?label=discord&style=flat-square)](https://appwrite.io/discord?r=Github)
[![Docker Pulls](https://img.shields.io/docker/pulls/appwrite/appwrite?color=f02e65&style=flat-square)](https://hub.docker.com/r/appwrite/appwrite) [![Docker Pulls](https://img.shields.io/docker/pulls/appwrite/appwrite?color=f02e65&style=flat-square)](https://hub.docker.com/r/appwrite/appwrite)
[![Build Status](https://img.shields.io/travis/com/appwrite/appwrite?style=flat-square)](https://travis-ci.com/appwrite/appwrite) [![Build Status](https://img.shields.io/travis/com/appwrite/appwrite?style=flat-square)](https://travis-ci.com/appwrite/appwrite)
[![Twitter Account](https://img.shields.io/twitter/follow/appwrite?color=00acee&label=twitter&style=flat-square)](https://twitter.com/appwrite) [![Twitter Account](https://img.shields.io/twitter/follow/appwrite?color=00acee&label=twitter&style=flat-square)](https://twitter.com/appwrite)
[![Translate](https://img.shields.io/badge/translate-f02e65?style=flat-square)](docs/tutorials/add-translations.md) [![Translate](https://img.shields.io/badge/translate-f02e65?style=flat-square)](docs/tutorials/add-translations.md)
<!-- [![Swag Store](https://img.shields.io/badge/swag%20store-f02e65?style=flat-square)](https://store.appwrite.io) --> [![Swag Store](https://img.shields.io/badge/swag%20store-f02e65?style=flat-square)](https://store.appwrite.io)
[**Appwrite 0.11 has been released! Learn what's new!**](https://dev.to/appwrite/building-apps-just-got-swifter-announcing-appwrite-v011-4g62) [**Appwrite 0.11 has been released! Learn what's new!**](https://dev.to/appwrite/building-apps-just-got-swifter-announcing-appwrite-v011-4g62)

View file

@ -43,6 +43,17 @@ $collections = [
'array' => false, 'array' => false,
'filters' => [], 'filters' => [],
], ],
[
'$id' => 'enabled',
'type' => Database::VAR_BOOLEAN,
'signed' => true,
'size' => 0,
'format' => '',
'filters' => [],
'required' => true,
'default' => null,
'array' => false,
],
[ [
'$id' => 'permission', '$id' => 'permission',
'type' => Database::VAR_STRING, 'type' => Database::VAR_STRING,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -669,26 +669,24 @@ App::post('/v1/account/sessions/magic-url')
$userId = $userId == 'unique()' ? $dbForInternal->getId() : $userId; $userId = $userId == 'unique()' ? $dbForInternal->getId() : $userId;
$user = Authorization::skip(function () use ($dbForInternal, $userId, $email) { $user = Authorization::skip(fn () => $dbForInternal->createDocument('users', new Document([
return $dbForInternal->createDocument('users', new Document([ '$id' => $userId,
'$id' => $userId, '$read' => ['role:all'],
'$read' => ['role:all'], '$write' => ['user:' . $userId],
'$write' => ['user:' . $userId], 'email' => $email,
'email' => $email, 'emailVerification' => false,
'emailVerification' => false, 'status' => true,
'status' => true, 'password' => null,
'password' => null, 'passwordUpdate' => \time(),
'passwordUpdate' => \time(), 'registration' => \time(),
'registration' => \time(), 'reset' => false,
'reset' => false, 'prefs' => [],
'prefs' => [], 'sessions' => [],
'sessions' => [], 'tokens' => [],
'tokens' => [], 'memberships' => [],
'memberships' => [], 'search' => implode(' ', [$userId, $email]),
'search' => implode(' ', [$userId, $email]), 'deleted' => false
'deleted' => false ])));
]));
});
$mails->setParam('event', 'users.create'); $mails->setParam('event', 'users.create');
$audits->setParam('event', 'users.create'); $audits->setParam('event', 'users.create');

View file

@ -95,9 +95,7 @@ App::get('/v1/avatars/credit-cards/:code')
->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true) ->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
->param('quality', 100, new Range(0, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to 100.', true) ->param('quality', 100, new Range(0, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to 100.', true)
->inject('response') ->inject('response')
->action(function ($code, $width, $height, $quality, $response) use ($avatarCallback) { ->action(fn($code, $width, $height, $quality, $response) => $avatarCallback('credit-cards', $code, $width, $height, $quality, $response));
return $avatarCallback('credit-cards', $code, $width, $height, $quality, $response);
});
App::get('/v1/avatars/browsers/:code') App::get('/v1/avatars/browsers/:code')
->desc('Get Browser Icon') ->desc('Get Browser Icon')
@ -115,9 +113,7 @@ App::get('/v1/avatars/browsers/:code')
->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true) ->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
->param('quality', 100, new Range(0, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to 100.', true) ->param('quality', 100, new Range(0, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to 100.', true)
->inject('response') ->inject('response')
->action(function ($code, $width, $height, $quality, $response) use ($avatarCallback) { ->action(fn($code, $width, $height, $quality, $response) => $avatarCallback('browsers', $code, $width, $height, $quality, $response));
return $avatarCallback('browsers', $code, $width, $height, $quality, $response);
});
App::get('/v1/avatars/flags/:code') App::get('/v1/avatars/flags/:code')
->desc('Get Country Flag') ->desc('Get Country Flag')
@ -135,9 +131,7 @@ App::get('/v1/avatars/flags/:code')
->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true) ->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
->param('quality', 100, new Range(0, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to 100.', true) ->param('quality', 100, new Range(0, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to 100.', true)
->inject('response') ->inject('response')
->action(function ($code, $width, $height, $quality, $response) use ($avatarCallback) { ->action(fn($code, $width, $height, $quality, $response) => $avatarCallback('flags', $code, $width, $height, $quality, $response));
return $avatarCallback('flags', $code, $width, $height, $quality, $response);
});
App::get('/v1/avatars/image') App::get('/v1/avatars/image')
->desc('Get Image from URL') ->desc('Get Image from URL')

View file

@ -50,7 +50,7 @@ use Appwrite\Detector\Detector;
*/ */
function createAttribute($collectionId, $attribute, $response, $dbForInternal, $dbForExternal, $database, $audits, $usage): Document function createAttribute($collectionId, $attribute, $response, $dbForInternal, $dbForExternal, $database, $audits, $usage): Document
{ {
$attributeId = $attribute->getId(); $key = $attribute->getAttribute('key');
$type = $attribute->getAttribute('type', ''); $type = $attribute->getAttribute('type', '');
$size = $attribute->getAttribute('size', 0); $size = $attribute->getAttribute('size', 0);
$required = $attribute->getAttribute('required', true); $required = $attribute->getAttribute('required', true);
@ -59,7 +59,7 @@ function createAttribute($collectionId, $attribute, $response, $dbForInternal, $
$format = $attribute->getAttribute('format', ''); $format = $attribute->getAttribute('format', '');
$formatOptions = $attribute->getAttribute('formatOptions', []); $formatOptions = $attribute->getAttribute('formatOptions', []);
$filters = $attribute->getAttribute('filters', []); // filters are hidden from the endpoint $filters = $attribute->getAttribute('filters', []); // filters are hidden from the endpoint
$default = $attribute->getAttribute('default', null); $default = $attribute->getAttribute('default');
$collection = $dbForInternal->getDocument('collections', $collectionId); $collection = $dbForInternal->getDocument('collections', $collectionId);
@ -84,8 +84,8 @@ function createAttribute($collectionId, $attribute, $response, $dbForInternal, $
try { try {
$attribute = new Document([ $attribute = new Document([
'$id' => $collectionId.'_'.$attributeId, '$id' => $collectionId.'_'.$key,
'key' => $attributeId, 'key' => $key,
'collectionId' => $collectionId, 'collectionId' => $collectionId,
'type' => $type, 'type' => $type,
'status' => 'processing', // processing, available, failed, deleting, stuck 'status' => 'processing', // processing, available, failed, deleting, stuck
@ -175,6 +175,7 @@ App::post('/v1/database/collections')
'permission' => $permission, // Permissions model type (document vs collection) 'permission' => $permission, // Permissions model type (document vs collection)
'dateCreated' => time(), 'dateCreated' => time(),
'dateUpdated' => time(), 'dateUpdated' => time(),
'enabled' => true,
'name' => $name, 'name' => $name,
'search' => implode(' ', [$collectionId, $name]), 'search' => implode(' ', [$collectionId, $name]),
])); ]));
@ -592,11 +593,12 @@ App::put('/v1/database/collections/:collectionId')
->param('permission', null, new WhiteList(['document', 'collection']), 'Permissions type model to use for reading documents in this collection. You can use collection-level permission set once on the collection using the `read` and `write` params, or you can set document-level permission where each document read and write params will decide who has access to read and write to each document individually. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.') ->param('permission', null, new WhiteList(['document', 'collection']), 'Permissions type model to use for reading documents in this collection. You can use collection-level permission set once on the collection using the `read` and `write` params, or you can set document-level permission where each document read and write params will decide who has access to read and write to each document individually. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.')
->param('read', null, new Permissions(), 'An array of strings with read permissions. By default inherits the existing read permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.', true) ->param('read', null, new Permissions(), 'An array of strings with read permissions. By default inherits the existing read permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.', true)
->param('write', null, new Permissions(), 'An array of strings with write permissions. By default inherits the existing write permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.', true) ->param('write', null, new Permissions(), 'An array of strings with write permissions. By default inherits the existing write permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.', true)
->param('enabled', true, new Boolean(), 'Is collection enabled?', true)
->inject('response') ->inject('response')
->inject('dbForInternal') ->inject('dbForInternal')
->inject('audits') ->inject('audits')
->inject('usage') ->inject('usage')
->action(function ($collectionId, $name, $permission, $read, $write, $response, $dbForInternal, $audits, $usage) { ->action(function ($collectionId, $name, $permission, $read, $write, $enabled, $response, $dbForInternal, $audits, $usage) {
/** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */ /** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Event\Event $audits */
@ -608,8 +610,9 @@ App::put('/v1/database/collections/:collectionId')
throw new Exception('Collection not found', 404); throw new Exception('Collection not found', 404);
} }
$read = (is_null($read)) ? ($collection->getRead() ?? []) : $read; // By default inherit read permissions $read ??= $collection->getRead() ?? []; // By default inherit read permissions
$write = (is_null($write)) ? ($collection->getWrite() ?? []) : $write; // By default inherit write permissions $write ??= $collection->getWrite() ?? []; // By default inherit write permissions
$enabled ??= $collection->getAttribute('enabled', true);
try { try {
$collection = $dbForInternal->updateDocument('collections', $collection->getId(), $collection $collection = $dbForInternal->updateDocument('collections', $collection->getId(), $collection
@ -618,6 +621,7 @@ App::put('/v1/database/collections/:collectionId')
->setAttribute('name', $name) ->setAttribute('name', $name)
->setAttribute('permission', $permission) ->setAttribute('permission', $permission)
->setAttribute('dateUpdated', time()) ->setAttribute('dateUpdated', time())
->setAttribute('enabled', $enabled)
->setAttribute('search', implode(' ', [$collectionId, $name])) ->setAttribute('search', implode(' ', [$collectionId, $name]))
); );
} }
@ -711,7 +715,7 @@ App::post('/v1/database/collections/:collectionId/attributes/string')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ATTRIBUTE_STRING) ->label('sdk.response.model', Response::MODEL_ATTRIBUTE_STRING)
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).') ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('attributeId', '', new Key(), 'Attribute ID.') ->param('key', '', new Key(), 'Attribute Key.')
->param('size', null, new Range(1, APP_DATABASE_ATTRIBUTE_STRING_MAX_LENGTH, Range::TYPE_INTEGER), 'Attribute size for text attributes, in number of characters.') ->param('size', null, new Range(1, APP_DATABASE_ATTRIBUTE_STRING_MAX_LENGTH, Range::TYPE_INTEGER), 'Attribute size for text attributes, in number of characters.')
->param('required', null, new Boolean(), 'Is attribute required?') ->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Text(0), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true) ->param('default', null, new Text(0), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true)
@ -722,7 +726,7 @@ App::post('/v1/database/collections/:collectionId/attributes/string')
->inject('database') ->inject('database')
->inject('audits') ->inject('audits')
->inject('usage') ->inject('usage')
->action(function ($collectionId, $attributeId, $size, $required, $default, $array, $response, $dbForInternal, $dbForExternal, $database, $audits, $usage) { ->action(function ($collectionId, $key, $size, $required, $default, $array, $response, $dbForInternal, $dbForExternal, $database, $audits, $usage) {
/** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal*/ /** @var Utopia\Database\Database $dbForInternal*/
/** @var Utopia\Database\Database $dbForExternal*/ /** @var Utopia\Database\Database $dbForExternal*/
@ -737,7 +741,7 @@ App::post('/v1/database/collections/:collectionId/attributes/string')
} }
$attribute = createAttribute($collectionId, new Document([ $attribute = createAttribute($collectionId, new Document([
'$id' => $attributeId, 'key' => $key,
'type' => Database::VAR_STRING, 'type' => Database::VAR_STRING,
'size' => $size, 'size' => $size,
'required' => $required, 'required' => $required,
@ -761,7 +765,7 @@ App::post('/v1/database/collections/:collectionId/attributes/email')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ATTRIBUTE_EMAIL) ->label('sdk.response.model', Response::MODEL_ATTRIBUTE_EMAIL)
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).') ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('attributeId', '', new Key(), 'Attribute ID.') ->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?') ->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Email(), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true) ->param('default', null, new Email(), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true)
->param('array', false, new Boolean(), 'Is attribute an array?', true) ->param('array', false, new Boolean(), 'Is attribute an array?', true)
@ -771,7 +775,7 @@ App::post('/v1/database/collections/:collectionId/attributes/email')
->inject('database') ->inject('database')
->inject('audits') ->inject('audits')
->inject('usage') ->inject('usage')
->action(function ($collectionId, $attributeId, $required, $default, $array, $response, $dbForInternal, $dbForExternal, $database, $audits, $usage) { ->action(function ($collectionId, $key, $required, $default, $array, $response, $dbForInternal, $dbForExternal, $database, $audits, $usage) {
/** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal*/ /** @var Utopia\Database\Database $dbForInternal*/
/** @var Utopia\Database\Database $dbForExternal*/ /** @var Utopia\Database\Database $dbForExternal*/
@ -780,7 +784,7 @@ App::post('/v1/database/collections/:collectionId/attributes/email')
/** @var Appwrite\Stats\Stats $usage */ /** @var Appwrite\Stats\Stats $usage */
$attribute = createAttribute($collectionId, new Document([ $attribute = createAttribute($collectionId, new Document([
'$id' => $attributeId, 'key' => $key,
'type' => Database::VAR_STRING, 'type' => Database::VAR_STRING,
'size' => 254, 'size' => 254,
'required' => $required, 'required' => $required,
@ -805,7 +809,7 @@ App::post('/v1/database/collections/:collectionId/attributes/enum')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ATTRIBUTE_ENUM) ->label('sdk.response.model', Response::MODEL_ATTRIBUTE_ENUM)
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).') ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('attributeId', '', new Key(), 'Attribute ID.') ->param('key', '', new Key(), 'Attribute Key.')
->param('elements', [], new ArrayList(new Text(0)), 'Array of elements in enumerated type. Uses length of longest element to determine size.') ->param('elements', [], new ArrayList(new Text(0)), 'Array of elements in enumerated type. Uses length of longest element to determine size.')
->param('required', null, new Boolean(), 'Is attribute required?') ->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Text(0), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true) ->param('default', null, new Text(0), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true)
@ -816,7 +820,7 @@ App::post('/v1/database/collections/:collectionId/attributes/enum')
->inject('database') ->inject('database')
->inject('audits') ->inject('audits')
->inject('usage') ->inject('usage')
->action(function ($collectionId, $attributeId, $elements, $required, $default, $array, $response, $dbForInternal, $dbForExternal, $database, $audits, $usage) { ->action(function ($collectionId, $key, $elements, $required, $default, $array, $response, $dbForInternal, $dbForExternal, $database, $audits, $usage) {
/** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal*/ /** @var Utopia\Database\Database $dbForInternal*/
/** @var Utopia\Database\Database $dbForExternal*/ /** @var Utopia\Database\Database $dbForExternal*/
@ -835,8 +839,12 @@ App::post('/v1/database/collections/:collectionId/attributes/enum')
$size = ($length > $size) ? $length : $size; $size = ($length > $size) ? $length : $size;
} }
if (!is_null($default) && !in_array($default, $elements)) {
throw new Exception('Default value not found in elements', 400);
}
$attribute = createAttribute($collectionId, new Document([ $attribute = createAttribute($collectionId, new Document([
'$id' => $attributeId, 'key' => $key,
'type' => Database::VAR_STRING, 'type' => Database::VAR_STRING,
'size' => $size, 'size' => $size,
'required' => $required, 'required' => $required,
@ -862,7 +870,7 @@ App::post('/v1/database/collections/:collectionId/attributes/ip')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ATTRIBUTE_IP) ->label('sdk.response.model', Response::MODEL_ATTRIBUTE_IP)
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).') ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('attributeId', '', new Key(), 'Attribute ID.') ->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?') ->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new IP(), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true) ->param('default', null, new IP(), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true)
->param('array', false, new Boolean(), 'Is attribute an array?', true) ->param('array', false, new Boolean(), 'Is attribute an array?', true)
@ -872,7 +880,7 @@ App::post('/v1/database/collections/:collectionId/attributes/ip')
->inject('database') ->inject('database')
->inject('audits') ->inject('audits')
->inject('usage') ->inject('usage')
->action(function ($collectionId, $attributeId, $required, $default, $array, $response, $dbForInternal, $dbForExternal, $database, $audits, $usage) { ->action(function ($collectionId, $key, $required, $default, $array, $response, $dbForInternal, $dbForExternal, $database, $audits, $usage) {
/** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal*/ /** @var Utopia\Database\Database $dbForInternal*/
/** @var Utopia\Database\Database $dbForExternal*/ /** @var Utopia\Database\Database $dbForExternal*/
@ -881,7 +889,7 @@ App::post('/v1/database/collections/:collectionId/attributes/ip')
/** @var Appwrite\Stats\Stats $usage */ /** @var Appwrite\Stats\Stats $usage */
$attribute = createAttribute($collectionId, new Document([ $attribute = createAttribute($collectionId, new Document([
'$id' => $attributeId, 'key' => $key,
'type' => Database::VAR_STRING, 'type' => Database::VAR_STRING,
'size' => 39, 'size' => 39,
'required' => $required, 'required' => $required,
@ -906,7 +914,7 @@ App::post('/v1/database/collections/:collectionId/attributes/url')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ATTRIBUTE_URL) ->label('sdk.response.model', Response::MODEL_ATTRIBUTE_URL)
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).') ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('attributeId', '', new Key(), 'Attribute ID.') ->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?') ->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new URL(), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true) ->param('default', null, new URL(), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true)
->param('array', false, new Boolean(), 'Is attribute an array?', true) ->param('array', false, new Boolean(), 'Is attribute an array?', true)
@ -916,7 +924,7 @@ App::post('/v1/database/collections/:collectionId/attributes/url')
->inject('database') ->inject('database')
->inject('audits') ->inject('audits')
->inject('usage') ->inject('usage')
->action(function ($collectionId, $attributeId, $required, $default, $array, $response, $dbForInternal, $dbForExternal, $database, $audits, $usage) { ->action(function ($collectionId, $key, $required, $default, $array, $response, $dbForInternal, $dbForExternal, $database, $audits, $usage) {
/** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForExternal*/ /** @var Utopia\Database\Database $dbForExternal*/
/** @var Appwrite\Event\Event $database */ /** @var Appwrite\Event\Event $database */
@ -924,7 +932,7 @@ App::post('/v1/database/collections/:collectionId/attributes/url')
/** @var Appwrite\Stats\Stats $usage */ /** @var Appwrite\Stats\Stats $usage */
$attribute = createAttribute($collectionId, new Document([ $attribute = createAttribute($collectionId, new Document([
'$id' => $attributeId, 'key' => $key,
'type' => Database::VAR_STRING, 'type' => Database::VAR_STRING,
'size' => 2000, 'size' => 2000,
'required' => $required, 'required' => $required,
@ -949,7 +957,7 @@ App::post('/v1/database/collections/:collectionId/attributes/integer')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ATTRIBUTE_INTEGER) ->label('sdk.response.model', Response::MODEL_ATTRIBUTE_INTEGER)
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).') ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('attributeId', '', new Key(), 'Attribute ID.') ->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?') ->param('required', null, new Boolean(), 'Is attribute required?')
->param('min', null, new Integer(), 'Minimum value to enforce on new documents', true) ->param('min', null, new Integer(), 'Minimum value to enforce on new documents', true)
->param('max', null, new Integer(), 'Maximum value to enforce on new documents', true) ->param('max', null, new Integer(), 'Maximum value to enforce on new documents', true)
@ -961,7 +969,7 @@ App::post('/v1/database/collections/:collectionId/attributes/integer')
->inject('database') ->inject('database')
->inject('audits') ->inject('audits')
->inject('usage') ->inject('usage')
->action(function ($collectionId, $attributeId, $required, $min, $max, $default, $array, $response, $dbForInternal, $dbForExternal, $database, $audits, $usage) { ->action(function ($collectionId, $key, $required, $min, $max, $default, $array, $response, $dbForInternal, $dbForExternal, $database, $audits, $usage) {
/** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal*/ /** @var Utopia\Database\Database $dbForInternal*/
/** @var Utopia\Database\Database $dbForExternal*/ /** @var Utopia\Database\Database $dbForExternal*/
@ -984,7 +992,7 @@ App::post('/v1/database/collections/:collectionId/attributes/integer')
} }
$attribute = createAttribute($collectionId, new Document([ $attribute = createAttribute($collectionId, new Document([
'$id' => $attributeId, 'key' => $key,
'type' => Database::VAR_INTEGER, 'type' => Database::VAR_INTEGER,
'size' => 0, 'size' => 0,
'required' => $required, 'required' => $required,
@ -1020,7 +1028,7 @@ App::post('/v1/database/collections/:collectionId/attributes/float')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ATTRIBUTE_FLOAT) ->label('sdk.response.model', Response::MODEL_ATTRIBUTE_FLOAT)
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).') ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('attributeId', '', new Key(), 'Attribute ID.') ->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?') ->param('required', null, new Boolean(), 'Is attribute required?')
->param('min', null, new FloatValidator(), 'Minimum value to enforce on new documents', true) ->param('min', null, new FloatValidator(), 'Minimum value to enforce on new documents', true)
->param('max', null, new FloatValidator(), 'Maximum value to enforce on new documents', true) ->param('max', null, new FloatValidator(), 'Maximum value to enforce on new documents', true)
@ -1032,7 +1040,7 @@ App::post('/v1/database/collections/:collectionId/attributes/float')
->inject('database') ->inject('database')
->inject('audits') ->inject('audits')
->inject('usage') ->inject('usage')
->action(function ($collectionId, $attributeId, $required, $min, $max, $default, $array, $response, $dbForInternal, $dbForExternal, $database, $audits, $usage) { ->action(function ($collectionId, $key, $required, $min, $max, $default, $array, $response, $dbForInternal, $dbForExternal, $database, $audits, $usage) {
/** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal*/ /** @var Utopia\Database\Database $dbForInternal*/
/** @var Utopia\Database\Database $dbForExternal*/ /** @var Utopia\Database\Database $dbForExternal*/
@ -1047,7 +1055,7 @@ App::post('/v1/database/collections/:collectionId/attributes/float')
if ($min > $max) { if ($min > $max) {
throw new Exception('Minimum value must be lesser than maximum value', 400); throw new Exception('Minimum value must be lesser than maximum value', 400);
} }
// Ensure default value is a float // Ensure default value is a float
if (!is_null($default)) { if (!is_null($default)) {
$default = \floatval($default); $default = \floatval($default);
@ -1060,7 +1068,7 @@ App::post('/v1/database/collections/:collectionId/attributes/float')
} }
$attribute = createAttribute($collectionId, new Document([ $attribute = createAttribute($collectionId, new Document([
'$id' => $attributeId, 'key' => $key,
'type' => Database::VAR_FLOAT, 'type' => Database::VAR_FLOAT,
'required' => $required, 'required' => $required,
'size' => 0, 'size' => 0,
@ -1096,7 +1104,7 @@ App::post('/v1/database/collections/:collectionId/attributes/boolean')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ATTRIBUTE_BOOLEAN) ->label('sdk.response.model', Response::MODEL_ATTRIBUTE_BOOLEAN)
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).') ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('attributeId', '', new Key(), 'Attribute ID.') ->param('key', '', new Key(), 'Attribute Key.')
->param('required', null, new Boolean(), 'Is attribute required?') ->param('required', null, new Boolean(), 'Is attribute required?')
->param('default', null, new Boolean(), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true) ->param('default', null, new Boolean(), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true)
->param('array', false, new Boolean(), 'Is attribute an array?', true) ->param('array', false, new Boolean(), 'Is attribute an array?', true)
@ -1106,7 +1114,7 @@ App::post('/v1/database/collections/:collectionId/attributes/boolean')
->inject('database') ->inject('database')
->inject('audits') ->inject('audits')
->inject('usage') ->inject('usage')
->action(function ($collectionId, $attributeId, $required, $default, $array, $response, $dbForInternal, $dbForExternal, $database, $audits, $usage) { ->action(function ($collectionId, $key, $required, $default, $array, $response, $dbForInternal, $dbForExternal, $database, $audits, $usage) {
/** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal*/ /** @var Utopia\Database\Database $dbForInternal*/
/** @var Appwrite\Event\Event $database */ /** @var Appwrite\Event\Event $database */
@ -1114,7 +1122,7 @@ App::post('/v1/database/collections/:collectionId/attributes/boolean')
/** @var Appwrite\Stats\Stats $usage */ /** @var Appwrite\Stats\Stats $usage */
$attribute = createAttribute($collectionId, new Document([ $attribute = createAttribute($collectionId, new Document([
'$id' => $attributeId, 'key' => $key,
'type' => Database::VAR_BOOLEAN, 'type' => Database::VAR_BOOLEAN,
'size' => 0, 'size' => 0,
'required' => $required, 'required' => $required,
@ -1160,7 +1168,7 @@ App::get('/v1/database/collections/:collectionId/attributes')
]), Response::MODEL_ATTRIBUTE_LIST); ]), Response::MODEL_ATTRIBUTE_LIST);
}); });
App::get('/v1/database/collections/:collectionId/attributes/:attributeId') App::get('/v1/database/collections/:collectionId/attributes/:key')
->desc('Get Attribute') ->desc('Get Attribute')
->groups(['api', 'database']) ->groups(['api', 'database'])
->label('scope', 'collections.read') ->label('scope', 'collections.read')
@ -1180,11 +1188,11 @@ App::get('/v1/database/collections/:collectionId/attributes/:attributeId')
Response::MODEL_ATTRIBUTE_IP, Response::MODEL_ATTRIBUTE_IP,
Response::MODEL_ATTRIBUTE_STRING,])// needs to be last, since its condition would dominate any other string attribute Response::MODEL_ATTRIBUTE_STRING,])// needs to be last, since its condition would dominate any other string attribute
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).') ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('attributeId', '', new Key(), 'Attribute ID.') ->param('key', '', new Key(), 'Attribute Key.')
->inject('response') ->inject('response')
->inject('dbForInternal') ->inject('dbForInternal')
->inject('usage') ->inject('usage')
->action(function ($collectionId, $attributeId, $response, $dbForInternal, $usage) { ->action(function ($collectionId, $key, $response, $dbForInternal, $usage) {
/** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */ /** @var Utopia\Database\Database $dbForInternal */
@ -1194,7 +1202,7 @@ App::get('/v1/database/collections/:collectionId/attributes/:attributeId')
throw new Exception('Collection not found', 404); throw new Exception('Collection not found', 404);
} }
$attribute = $collection->find('$id', $attributeId, 'attributes'); $attribute = $collection->find('$id', $key, 'attributes');
if (!$attribute) { if (!$attribute) {
throw new Exception('Attribute not found', 404); throw new Exception('Attribute not found', 404);
@ -1223,7 +1231,7 @@ App::get('/v1/database/collections/:collectionId/attributes/:attributeId')
$response->dynamic($attribute, $model); $response->dynamic($attribute, $model);
}); });
App::delete('/v1/database/collections/:collectionId/attributes/:attributeId') App::delete('/v1/database/collections/:collectionId/attributes/:key')
->desc('Delete Attribute') ->desc('Delete Attribute')
->groups(['api', 'database']) ->groups(['api', 'database'])
->label('scope', 'collections.write') ->label('scope', 'collections.write')
@ -1235,7 +1243,7 @@ App::delete('/v1/database/collections/:collectionId/attributes/:attributeId')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE) ->label('sdk.response.model', Response::MODEL_NONE)
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).') ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('attributeId', '', new Key(), 'Attribute ID.') ->param('key', '', new Key(), 'Attribute Key.')
->inject('response') ->inject('response')
->inject('dbForInternal') ->inject('dbForInternal')
->inject('dbForExternal') ->inject('dbForExternal')
@ -1243,7 +1251,7 @@ App::delete('/v1/database/collections/:collectionId/attributes/:attributeId')
->inject('events') ->inject('events')
->inject('audits') ->inject('audits')
->inject('usage') ->inject('usage')
->action(function ($collectionId, $attributeId, $response, $dbForInternal, $dbForExternal, $database, $events, $audits, $usage) { ->action(function ($collectionId, $key, $response, $dbForInternal, $dbForExternal, $database, $events, $audits, $usage) {
/** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */ /** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForExternal */ /** @var Utopia\Database\Database $dbForExternal */
@ -1258,7 +1266,7 @@ App::delete('/v1/database/collections/:collectionId/attributes/:attributeId')
throw new Exception('Collection not found', 404); throw new Exception('Collection not found', 404);
} }
$attribute = $dbForInternal->getDocument('attributes', $collectionId.'_'.$attributeId); $attribute = $dbForInternal->getDocument('attributes', $collectionId.'_'.$key);
if (empty($attribute->getId())) { if (empty($attribute->getId())) {
throw new Exception('Attribute not found', 404); throw new Exception('Attribute not found', 404);
@ -1324,7 +1332,7 @@ App::post('/v1/database/collections/:collectionId/indexes')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_INDEX) ->label('sdk.response.model', Response::MODEL_INDEX)
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).') ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('indexId', null, new Key(), 'Index ID.') ->param('key', null, new Key(), 'Index Key.')
->param('type', null, new WhiteList([Database::INDEX_KEY, Database::INDEX_FULLTEXT, Database::INDEX_UNIQUE, Database::INDEX_SPATIAL, Database::INDEX_ARRAY]), 'Index type.') ->param('type', null, new WhiteList([Database::INDEX_KEY, Database::INDEX_FULLTEXT, Database::INDEX_UNIQUE, Database::INDEX_SPATIAL, Database::INDEX_ARRAY]), 'Index type.')
->param('attributes', null, new ArrayList(new Key()), 'Array of attributes to index.') ->param('attributes', null, new ArrayList(new Key()), 'Array of attributes to index.')
->param('orders', [], new ArrayList(new WhiteList(['ASC', 'DESC'], false, Database::VAR_STRING)), 'Array of index orders.', true) ->param('orders', [], new ArrayList(new WhiteList(['ASC', 'DESC'], false, Database::VAR_STRING)), 'Array of index orders.', true)
@ -1333,7 +1341,7 @@ App::post('/v1/database/collections/:collectionId/indexes')
->inject('database') ->inject('database')
->inject('audits') ->inject('audits')
->inject('usage') ->inject('usage')
->action(function ($collectionId, $indexId, $type, $attributes, $orders, $response, $dbForInternal, $database, $audits, $usage) { ->action(function ($collectionId, $key, $type, $attributes, $orders, $response, $dbForInternal, $database, $audits, $usage) {
/** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */ /** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Event\Event $database */ /** @var Appwrite\Event\Event $database */
@ -1364,7 +1372,7 @@ App::post('/v1/database/collections/:collectionId/indexes')
// lengths hidden by default // lengths hidden by default
$lengths = []; $lengths = [];
foreach ($attributes as $key => $attribute) { foreach ($attributes as $i => $attribute) {
// find attribute metadata in collection document // find attribute metadata in collection document
$attributeIndex = \array_search($attribute, array_column($oldAttributes, 'key')); $attributeIndex = \array_search($attribute, array_column($oldAttributes, 'key'));
@ -1382,13 +1390,13 @@ App::post('/v1/database/collections/:collectionId/indexes')
} }
// set attribute size as index length only for strings // set attribute size as index length only for strings
$lengths[$key] = ($attributeType === Database::VAR_STRING) ? $attributeSize : null; $lengths[$i] = ($attributeType === Database::VAR_STRING) ? $attributeSize : null;
} }
try { try {
$index = $dbForInternal->createDocument('indexes', new Document([ $index = $dbForInternal->createDocument('indexes', new Document([
'$id' => $collectionId.'_'.$indexId, '$id' => $collectionId.'_'.$key,
'key' => $indexId, 'key' => $key,
'status' => 'processing', // processing, available, failed, deleting, stuck 'status' => 'processing', // processing, available, failed, deleting, stuck
'collectionId' => $collectionId, 'collectionId' => $collectionId,
'type' => $type, 'type' => $type,
@ -1455,7 +1463,7 @@ App::get('/v1/database/collections/:collectionId/indexes')
]), Response::MODEL_INDEX_LIST); ]), Response::MODEL_INDEX_LIST);
}); });
App::get('/v1/database/collections/:collectionId/indexes/:indexId') App::get('/v1/database/collections/:collectionId/indexes/:key')
->desc('Get Index') ->desc('Get Index')
->groups(['api', 'database']) ->groups(['api', 'database'])
->label('scope', 'collections.read') ->label('scope', 'collections.read')
@ -1467,11 +1475,11 @@ App::get('/v1/database/collections/:collectionId/indexes/:indexId')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_INDEX) ->label('sdk.response.model', Response::MODEL_INDEX)
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).') ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('indexId', null, new Key(), 'Index ID.') ->param('key', null, new Key(), 'Index Key.')
->inject('response') ->inject('response')
->inject('dbForInternal') ->inject('dbForInternal')
->inject('usage') ->inject('usage')
->action(function ($collectionId, $indexId, $response, $dbForInternal, $usage) { ->action(function ($collectionId, $key, $response, $dbForInternal, $usage) {
/** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */ /** @var Utopia\Database\Database $dbForInternal */
@ -1484,7 +1492,7 @@ App::get('/v1/database/collections/:collectionId/indexes/:indexId')
$indexes = $collection->getAttribute('indexes'); $indexes = $collection->getAttribute('indexes');
// Search for index // Search for index
$indexIndex = array_search($indexId, array_column($indexes, '$id')); $indexIndex = array_search($key, array_column($indexes, 'key'));
if ($indexIndex === false) { if ($indexIndex === false) {
throw new Exception('Index not found', 404); throw new Exception('Index not found', 404);
@ -1495,11 +1503,11 @@ App::get('/v1/database/collections/:collectionId/indexes/:indexId')
])]); ])]);
$usage->setParam('database.collections.read', 1); $usage->setParam('database.collections.read', 1);
$response->dynamic($index, Response::MODEL_INDEX); $response->dynamic($index, Response::MODEL_INDEX);
}); });
App::delete('/v1/database/collections/:collectionId/indexes/:indexId') App::delete('/v1/database/collections/:collectionId/indexes/:key')
->desc('Delete Index') ->desc('Delete Index')
->groups(['api', 'database']) ->groups(['api', 'database'])
->label('scope', 'collections.write') ->label('scope', 'collections.write')
@ -1511,14 +1519,14 @@ App::delete('/v1/database/collections/:collectionId/indexes/:indexId')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE) ->label('sdk.response.model', Response::MODEL_NONE)
->param('collectionId', null, new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).') ->param('collectionId', null, new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/database#createCollection).')
->param('indexId', '', new Key(), 'Index ID.') ->param('key', '', new Key(), 'Index Key.')
->inject('response') ->inject('response')
->inject('dbForInternal') ->inject('dbForInternal')
->inject('database') ->inject('database')
->inject('events') ->inject('events')
->inject('audits') ->inject('audits')
->inject('usage') ->inject('usage')
->action(function ($collectionId, $indexId, $response, $dbForInternal, $database, $events, $audits, $usage) { ->action(function ($collectionId, $key, $response, $dbForInternal, $database, $events, $audits, $usage) {
/** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */ /** @var Utopia\Database\Database $dbForInternal */
/** @var Appwrite\Event\Event $database */ /** @var Appwrite\Event\Event $database */
@ -1532,7 +1540,7 @@ App::delete('/v1/database/collections/:collectionId/indexes/:indexId')
throw new Exception('Collection not found', 404); throw new Exception('Collection not found', 404);
} }
$index = $dbForInternal->getDocument('indexes', $collectionId.'_'.$indexId); $index = $dbForInternal->getDocument('indexes', $collectionId.'_'.$key);
if (empty($index->getId())) { if (empty($index->getId())) {
throw new Exception('Index not found', 404); throw new Exception('Index not found', 404);
@ -1589,13 +1597,17 @@ App::post('/v1/database/collections/:collectionId/documents')
->inject('user') ->inject('user')
->inject('audits') ->inject('audits')
->inject('usage') ->inject('usage')
->action(function ($documentId, $collectionId, $data, $read, $write, $response, $dbForInternal, $dbForExternal, $user, $audits, $usage) { ->inject('events')
->inject('mode')
->action(function ($documentId, $collectionId, $data, $read, $write, $response, $dbForInternal, $dbForExternal, $user, $audits, $usage, $events, $mode) {
/** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */ /** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForExternal */ /** @var Utopia\Database\Database $dbForExternal */
/** @var Utopia\Database\Document $user */ /** @var Utopia\Database\Document $user */
/** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Stats\Stats $usage */ /** @var Appwrite\Stats\Stats $usage */
/** @var Appwrite\Event\Event $events */
/** @var string $mode */
$data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array
@ -1607,10 +1619,15 @@ App::post('/v1/database/collections/:collectionId/documents')
throw new Exception('$id is not allowed for creating new documents, try update instead', 400); throw new Exception('$id is not allowed for creating new documents, try update instead', 400);
} }
$collection = $dbForInternal->getDocument('collections', $collectionId); /**
* Skip Authorization to get the collection. Needed in case of empty permissions for document level permissions.
*/
$collection = Authorization::skip(fn() => $dbForInternal->getDocument('collections', $collectionId));
if ($collection->isEmpty()) { if ($collection->isEmpty() || !$collection->getAttribute('enabled')) {
throw new Exception('Collection not found', 404); if (!($mode === APP_MODE_ADMIN && Auth::isPrivilegedUser(Authorization::getRoles()))) {
throw new Exception('Collection not found', 404);
}
} }
// Check collection permissions when enforced // Check collection permissions when enforced
@ -1645,9 +1662,7 @@ App::post('/v1/database/collections/:collectionId/documents')
try { try {
if ($collection->getAttribute('permission') === 'collection') { if ($collection->getAttribute('permission') === 'collection') {
/** @var Document $document */ /** @var Document $document */
$document = Authorization::skip(function() use ($dbForExternal, $collectionId, $data) { $document = Authorization::skip(fn() => $dbForExternal->createDocument($collectionId, new Document($data)));
return $dbForExternal->createDocument($collectionId, new Document($data));
});
} else { } else {
$document = $dbForExternal->createDocument($collectionId, new Document($data)); $document = $dbForExternal->createDocument($collectionId, new Document($data));
} }
@ -1659,6 +1674,8 @@ App::post('/v1/database/collections/:collectionId/documents')
throw new Exception('Document already exists', 409); throw new Exception('Document already exists', 409);
} }
$events->setParam('collection', $collection->getArrayCopy());
$usage $usage
->setParam('database.documents.create', 1) ->setParam('database.documents.create', 1)
->setParam('collectionId', $collectionId) ->setParam('collectionId', $collectionId)
@ -1696,17 +1713,28 @@ App::get('/v1/database/collections/:collectionId/documents')
->inject('response') ->inject('response')
->inject('dbForInternal') ->inject('dbForInternal')
->inject('dbForExternal') ->inject('dbForExternal')
->inject('user')
->inject('usage') ->inject('usage')
->action(function ($collectionId, $queries, $limit, $offset, $cursor, $cursorDirection, $orderAttributes, $orderTypes, $response, $dbForInternal, $dbForExternal, $usage) { ->inject('mode')
->action(function ($collectionId, $queries, $limit, $offset, $cursor, $cursorDirection, $orderAttributes, $orderTypes, $response, $dbForInternal, $dbForExternal, $user, $usage, $mode) {
/** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */ /** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForExternal */ /** @var Utopia\Database\Database $dbForExternal */
/** @var Appwrite\Stats\Stats $usage */ /** @var Appwrite\Stats\Stats $usage */
/** @var Utopia\Database\Document $user */
/** @var string $mode */
$collection = $dbForInternal->getDocument('collections', $collectionId); /**
* Skip Authorization to get the collection. Needed in case of empty permissions for document level permissions.
*
* @var Utopia\Database\Document $collection
*/
$collection = Authorization::skip(fn() => $dbForInternal->getDocument('collections', $collectionId));
if ($collection->isEmpty()) { if ($collection->isEmpty() || !$collection->getAttribute('enabled')) {
throw new Exception('Collection not found', 404); if (!($mode === APP_MODE_ADMIN && Auth::isPrivilegedUser(Authorization::getRoles()))) {
throw new Exception('Collection not found', 404);
}
} }
// Check collection permissions when enforced // Check collection permissions when enforced
@ -1739,11 +1767,11 @@ App::get('/v1/database/collections/:collectionId/documents')
if ($collection->getAttribute('permission') === 'collection') { if ($collection->getAttribute('permission') === 'collection') {
/** @var Document[] $documents */ /** @var Document[] $documents */
$documents = Authorization::skip(function() use ($dbForExternal, $collectionId, $queries, $limit, $offset, $orderAttributes, $orderTypes, $cursorDocument, $cursorDirection) { $documents = Authorization::skip(fn() => $dbForExternal->find($collectionId, $queries, $limit, $offset, $orderAttributes, $orderTypes, $cursorDocument ?? null, $cursorDirection));
return $dbForExternal->find($collectionId, $queries, $limit, $offset, $orderAttributes, $orderTypes, $cursorDocument ?? null, $cursorDirection); $sum = Authorization::skip(fn() => $dbForExternal->count($collectionId, $queries, APP_LIMIT_COUNT));
});
} else { } else {
$documents = $dbForExternal->find($collectionId, $queries, $limit, $offset, $orderAttributes, $orderTypes, $cursorDocument ?? null, $cursorDirection); $documents = $dbForExternal->find($collectionId, $queries, $limit, $offset, $orderAttributes, $orderTypes, $cursorDocument ?? null, $cursorDirection);
$sum = $dbForExternal->count($collectionId, $queries, APP_LIMIT_COUNT);
} }
$usage $usage
@ -1752,7 +1780,7 @@ App::get('/v1/database/collections/:collectionId/documents')
; ;
$response->dynamic(new Document([ $response->dynamic(new Document([
'sum' => $dbForExternal->count($collectionId, $queries, APP_LIMIT_COUNT), 'sum' => $sum,
'documents' => $documents, 'documents' => $documents,
]), Response::MODEL_DOCUMENT_LIST); ]), Response::MODEL_DOCUMENT_LIST);
}); });
@ -1774,15 +1802,22 @@ App::get('/v1/database/collections/:collectionId/documents/:documentId')
->inject('dbForInternal') ->inject('dbForInternal')
->inject('dbForExternal') ->inject('dbForExternal')
->inject('usage') ->inject('usage')
->action(function ($collectionId, $documentId, $response, $dbForInternal, $dbForExternal, $usage) { ->inject('mode')
->action(function ($collectionId, $documentId, $response, $dbForInternal, $dbForExternal, $usage, $mode) {
/** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $$dbForInternal */ /** @var Utopia\Database\Database $$dbForInternal */
/** @var Utopia\Database\Database $dbForExternal */ /** @var Utopia\Database\Database $dbForExternal */
/** @var string $mode */
$collection = $dbForInternal->getDocument('collections', $collectionId); /**
* Skip Authorization to get the collection. Needed in case of empty permissions for document level permissions.
*/
$collection = Authorization::skip(fn() => $dbForInternal->getDocument('collections', $collectionId));
if ($collection->isEmpty()) { if ($collection->isEmpty() || !$collection->getAttribute('enabled')) {
throw new Exception('Collection not found', 404); if (!($mode === APP_MODE_ADMIN && Auth::isPrivilegedUser(Authorization::getRoles()))) {
throw new Exception('Collection not found', 404);
}
} }
// Check collection permissions when enforced // Check collection permissions when enforced
@ -1795,9 +1830,7 @@ App::get('/v1/database/collections/:collectionId/documents/:documentId')
if ($collection->getAttribute('permission') === 'collection') { if ($collection->getAttribute('permission') === 'collection') {
/** @var Document $document */ /** @var Document $document */
$document = Authorization::skip(function() use ($dbForExternal, $collectionId, $documentId) { $document = Authorization::skip(fn() => $dbForExternal->getDocument($collectionId, $documentId));
return $dbForExternal->getDocument($collectionId, $documentId);
});
} else { } else {
$document = $dbForExternal->getDocument($collectionId, $documentId); $document = $dbForExternal->getDocument($collectionId, $documentId);
} }
@ -1930,17 +1963,26 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId')
->inject('dbForExternal') ->inject('dbForExternal')
->inject('audits') ->inject('audits')
->inject('usage') ->inject('usage')
->action(function ($collectionId, $documentId, $data, $read, $write, $response, $dbForInternal, $dbForExternal, $audits, $usage) { ->inject('events')
->inject('mode')
->action(function ($collectionId, $documentId, $data, $read, $write, $response, $dbForInternal, $dbForExternal, $audits, $usage, $events, $mode) {
/** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */ /** @var Utopia\Database\Database $dbForInternal */
/** @var Utopia\Database\Database $dbForExternal */ /** @var Utopia\Database\Database $dbForExternal */
/** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Stats\Stats $usage */ /** @var Appwrite\Stats\Stats $usage */
/** @var Appwrite\Event\Event $events */
/** @var string $mode */
$collection = $dbForInternal->getDocument('collections', $collectionId); /**
* Skip Authorization to get the collection. Needed in case of empty permissions for document level permissions.
*/
$collection = Authorization::skip(fn() => $dbForInternal->getDocument('collections', $collectionId));
if ($collection->isEmpty()) { if ($collection->isEmpty() || !$collection->getAttribute('enabled')) {
throw new Exception('Collection not found', 404); if (!($mode === APP_MODE_ADMIN && Auth::isPrivilegedUser(Authorization::getRoles()))) {
throw new Exception('Collection not found', 404);
}
} }
// Check collection permissions when enforced // Check collection permissions when enforced
@ -1949,9 +1991,12 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId')
if (!$validator->isValid($collection->getWrite())) { if (!$validator->isValid($collection->getWrite())) {
throw new Exception('Unauthorized permissions', 401); throw new Exception('Unauthorized permissions', 401);
} }
$document = Authorization::skip(fn() => $dbForExternal->getDocument($collectionId, $documentId));
} else {
$document = $dbForExternal->getDocument($collectionId, $documentId);
} }
$document = $dbForExternal->getDocument($collectionId, $documentId);
if ($document->isEmpty()) { if ($document->isEmpty()) {
throw new Exception('Document not found', 404); throw new Exception('Document not found', 404);
@ -1993,9 +2038,7 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId')
try { try {
if ($collection->getAttribute('permission') === 'collection') { if ($collection->getAttribute('permission') === 'collection') {
/** @var Document $document */ /** @var Document $document */
$document = Authorization::skip(function() use ($dbForExternal, $collection, $document, $data) { $document = Authorization::skip(fn() => $dbForExternal->updateDocument($collection->getId(), $document->getId(), new Document($data)));
return $dbForExternal->updateDocument($collection->getId(), $document->getId(), new Document($data));
});
} else { } else {
$document = $dbForExternal->updateDocument($collection->getId(), $document->getId(), new Document($data)); $document = $dbForExternal->updateDocument($collection->getId(), $document->getId(), new Document($data));
} }
@ -2009,7 +2052,9 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId')
catch (StructureException $exception) { catch (StructureException $exception) {
throw new Exception($exception->getMessage(), 400); throw new Exception($exception->getMessage(), 400);
} }
$events->setParam('collection', $collection->getArrayCopy());
$usage $usage
->setParam('database.documents.update', 1) ->setParam('database.documents.update', 1)
->setParam('collectionId', $collectionId) ->setParam('collectionId', $collectionId)
@ -2043,17 +2088,24 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId')
->inject('events') ->inject('events')
->inject('audits') ->inject('audits')
->inject('usage') ->inject('usage')
->action(function ($collectionId, $documentId, $response, $dbForInternal, $dbForExternal, $events, $audits, $usage) { ->inject('mode')
->action(function ($collectionId, $documentId, $response, $dbForInternal, $dbForExternal, $events, $audits, $usage, $mode) {
/** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForExternal */ /** @var Utopia\Database\Database $dbForExternal */
/** @var Appwrite\Event\Event $events */ /** @var Appwrite\Event\Event $events */
/** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Event\Event $audits */
/** @var Appwrite\Stats\Stats $usage */ /** @var Appwrite\Stats\Stats $usage */
/** @var string $mode */
$collection = $dbForInternal->getDocument('collections', $collectionId); /**
* Skip Authorization to get the collection. Needed in case of empty permissions for document level permissions.
*/
$collection = Authorization::skip(fn() => $dbForInternal->getDocument('collections', $collectionId));
if ($collection->isEmpty()) { if ($collection->isEmpty() || !$collection->getAttribute('enabled')) {
throw new Exception('Collection not found', 404); if (!($mode === APP_MODE_ADMIN && Auth::isPrivilegedUser(Authorization::getRoles()))) {
throw new Exception('Collection not found', 404);
}
} }
// Check collection permissions when enforced // Check collection permissions when enforced
@ -2066,9 +2118,7 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId')
if ($collection->getAttribute('permission') === 'collection') { if ($collection->getAttribute('permission') === 'collection') {
/** @var Document $document */ /** @var Document $document */
$document = Authorization::skip(function() use ($dbForExternal, $collectionId, $documentId) { $document = Authorization::skip(fn() => $dbForExternal->getDocument($collectionId, $documentId));
return $dbForExternal->getDocument($collectionId, $documentId);
});
} else { } else {
$document = $dbForExternal->getDocument($collectionId, $documentId); $document = $dbForExternal->getDocument($collectionId, $documentId);
} }
@ -2077,7 +2127,12 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId')
throw new Exception('No document found', 404); throw new Exception('No document found', 404);
} }
$dbForExternal->deleteDocument($collectionId, $documentId); if ($collection->getAttribute('permission') === 'collection') {
Authorization::skip(fn() => $dbForExternal->deleteDocument($collectionId, $documentId));
} else {
$dbForExternal->deleteDocument($collectionId, $documentId);
}
$dbForExternal->deleteCachedDocument($collectionId, $documentId); $dbForExternal->deleteCachedDocument($collectionId, $documentId);
$usage $usage
@ -2087,6 +2142,7 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId')
$events $events
->setParam('eventData', $response->output($document, Response::MODEL_DOCUMENT)) ->setParam('eventData', $response->output($document, Response::MODEL_DOCUMENT))
->setParam('collection', $collection->getArrayCopy());
; ;
$audits $audits

View file

@ -807,9 +807,7 @@ App::get('/v1/functions/:functionId/executions')
/** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForInternal */ /** @var Utopia\Database\Database $dbForInternal */
$function = Authorization::skip(function() use ($dbForInternal, $functionId) { $function = Authorization::skip(fn() => $dbForInternal->getDocument('functions', $functionId));
return $dbForInternal->getDocument('functions', $functionId);
});
if ($function->isEmpty()) { if ($function->isEmpty()) {
throw new Exception('Function not found', 404); throw new Exception('Function not found', 404);

View file

@ -179,7 +179,7 @@ App::put('/v1/teams/:teamId')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_TEAM) ->label('sdk.response.model', Response::MODEL_TEAM)
->param('teamId', '', new UID(), 'Team ID.') ->param('teamId', '', new UID(), 'Team ID.')
->param('name', null, new Text(128), 'Team name. Max length: 128 chars.') ->param('name', null, new Text(128), 'New team name. Max length: 128 chars.')
->inject('response') ->inject('response')
->inject('dbForInternal') ->inject('dbForInternal')
->action(function ($teamId, $name, $response, $dbForInternal) { ->action(function ($teamId, $name, $response, $dbForInternal) {
@ -270,10 +270,10 @@ App::post('/v1/teams/:teamId/memberships')
->label('sdk.response.model', Response::MODEL_MEMBERSHIP) ->label('sdk.response.model', Response::MODEL_MEMBERSHIP)
->label('abuse-limit', 10) ->label('abuse-limit', 10)
->param('teamId', '', new UID(), 'Team ID.') ->param('teamId', '', new UID(), 'Team ID.')
->param('email', '', new Email(), 'Team member email.') ->param('email', '', new Email(), 'Email of the new team member.')
->param('roles', [], new ArrayList(new Key()), 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](/docs/permissions). Max length for each role is 32 chars.') ->param('roles', [], new ArrayList(new Key()), 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](/docs/permissions). Max length for each role is 32 chars.')
->param('url', '', function ($clients) { return new Host($clients); }, 'URL to redirect the user back to your app from the invitation email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['clients']) // TODO add our own built-in confirm page ->param('url', '', function ($clients) { return new Host($clients); }, 'URL to redirect the user back to your app from the invitation email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['clients']) // TODO add our own built-in confirm page
->param('name', '', new Text(128), 'Team member name. Max length: 128 chars.', true) ->param('name', '', new Text(128), 'Name of the new team member. Max length: 128 chars.', true)
->inject('response') ->inject('response')
->inject('project') ->inject('project')
->inject('user') ->inject('user')
@ -470,20 +470,22 @@ App::get('/v1/teams/:teamId/memberships')
$memberships = $dbForInternal->find('memberships', [new Query('teamId', Query::TYPE_EQUAL, [$teamId])], $limit, $offset, [], [$orderType], $cursorMembership ?? null, $cursorDirection); $memberships = $dbForInternal->find('memberships', [new Query('teamId', Query::TYPE_EQUAL, [$teamId])], $limit, $offset, [], [$orderType], $cursorMembership ?? null, $cursorDirection);
$sum = $dbForInternal->count('memberships', [new Query('teamId', Query::TYPE_EQUAL, [$teamId])], APP_LIMIT_COUNT); $sum = $dbForInternal->count('memberships', [new Query('teamId', Query::TYPE_EQUAL, [$teamId])], APP_LIMIT_COUNT);
$users = [];
foreach ($memberships as $membership) { $memberships = array_filter($memberships, fn(Document $membership) => !empty($membership->getAttribute('userId')));
if (empty($membership->getAttribute('userId', null))) {
continue;
}
$temp = $dbForInternal->getDocument('users', $membership->getAttribute('userId', null))->getArrayCopy(['email', 'name']); $memberships = array_map(function($membership) use ($dbForInternal) {
$user = $dbForInternal->getDocument('users', $membership->getAttribute('userId'));
$users[] = new Document(\array_merge($temp, $membership->getArrayCopy())); $membership
} ->setAttribute('name', $user->getAttribute('name'))
->setAttribute('email', $user->getAttribute('email'))
;
return $membership;
}, $memberships);
$response->dynamic(new Document([ $response->dynamic(new Document([
'memberships' => $users, 'memberships' => $memberships,
'sum' => $sum, 'sum' => $sum,
]), Response::MODEL_MEMBERSHIP_LIST); ]), Response::MODEL_MEMBERSHIP_LIST);
}); });
@ -515,13 +517,18 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
$membership = $dbForInternal->getDocument('memberships', $membershipId); $membership = $dbForInternal->getDocument('memberships', $membershipId);
if($membership->isEmpty() || empty($membership->getAttribute('userId', null))) { if($membership->isEmpty() || empty($membership->getAttribute('userId'))) {
throw new Exception('Membership not found', 404); throw new Exception('Membership not found', 404);
} }
$temp = $dbForInternal->getDocument('users', $membership->getAttribute('userId', null))->getArrayCopy(['email', 'name']); $user = $dbForInternal->getDocument('users', $membership->getAttribute('userId'));
$response->dynamic(new Document(\array_merge($temp, $membership->getArrayCopy())), Response::MODEL_MEMBERSHIP ); $membership
->setAttribute('name', $user->getAttribute('name'))
->setAttribute('email', $user->getAttribute('email'))
;
$response->dynamic($membership, Response::MODEL_MEMBERSHIP );
}); });
App::patch('/v1/teams/:teamId/memberships/:membershipId') App::patch('/v1/teams/:teamId/memberships/:membershipId')
@ -538,7 +545,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
->label('sdk.response.model', Response::MODEL_MEMBERSHIP) ->label('sdk.response.model', Response::MODEL_MEMBERSHIP)
->param('teamId', '', new UID(), 'Team ID.') ->param('teamId', '', new UID(), 'Team ID.')
->param('membershipId', '', new UID(), 'Membership ID.') ->param('membershipId', '', new UID(), 'Membership ID.')
->param('roles', [], new ArrayList(new Key()), 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Max length for each role is 32 chars.') ->param('roles', [], new ArrayList(new Key()), 'An array of strings. Use this param to set the user\'s roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Max length for each role is 32 chars.')
->inject('request') ->inject('request')
->inject('response') ->inject('response')
->inject('user') ->inject('user')
@ -569,7 +576,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
$isAppUser = Auth::isAppUser(Authorization::getRoles()); $isAppUser = Auth::isAppUser(Authorization::getRoles());
$isOwner = Authorization::isRole('team:'.$team->getId().'/owner');; $isOwner = Authorization::isRole('team:'.$team->getId().'/owner');;
if (!$isOwner && !$isPrivilegedUser && !$isAppUser) { // Not owner, not admin, not app (server) if (!$isOwner && !$isPrivilegedUser && !$isAppUser) { // Not owner, not admin, not app (server)
throw new Exception('User is not allowed to modify roles', 401); throw new Exception('User is not allowed to modify roles', 401);
} }
@ -631,11 +638,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
throw new Exception('Team IDs don\'t match', 404); throw new Exception('Team IDs don\'t match', 404);
} }
Authorization::disable(); $team = Authorization::skip(fn() => $dbForInternal->getDocument('teams', $teamId));
$team = $dbForInternal->getDocument('teams', $teamId);
Authorization::reset();
if ($team->isEmpty()) { if ($team->isEmpty()) {
throw new Exception('Team not found', 404); throw new Exception('Team not found', 404);
@ -691,7 +694,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
->setAttribute('$read', ['user:'.$user->getId()]) ->setAttribute('$read', ['user:'.$user->getId()])
->setAttribute('$write', ['user:'.$user->getId()]) ->setAttribute('$write', ['user:'.$user->getId()])
); );
$user->setAttribute('sessions', $session, Document::SET_TYPE_APPEND); $user->setAttribute('sessions', $session, Document::SET_TYPE_APPEND);
Authorization::setRole('user:'.$userId); Authorization::setRole('user:'.$userId);

View file

@ -207,7 +207,14 @@ App::shutdown(function ($utopia, $request, $response, $project, $events, $audits
if ($project->getId() !== 'console') { if ($project->getId() !== 'console') {
$payload = new Document($response->getPayload()); $payload = new Document($response->getPayload());
$target = Realtime::fromPayload($events->getParam('event'), $payload, $project); $collection = new Document($events->getParam('collection') ?? []);
$target = Realtime::fromPayload(
event: $events->getParam('event'),
payload: $payload,
project: $project,
collection: $collection
);
Realtime::send( Realtime::send(
$target['projectId'] ?? $project->getId(), $target['projectId'] ?? $project->getId(),

View file

@ -73,17 +73,8 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
} }
} while ($attempts < $max); } while ($attempts < $max);
App::setResource('db', function () use (&$db) { App::setResource('db', fn() => $db);
return $db; App::setResource('cache', fn() => $redis);
});
App::setResource('cache', function () use (&$redis) {
return $redis;
});
App::setResource('app', function() use (&$app) {
return $app;
});
$dbForConsole = $app->getResource('dbForConsole'); /** @var Utopia\Database\Database $dbForConsole */ $dbForConsole = $app->getResource('dbForConsole'); /** @var Utopia\Database\Database $dbForConsole */
@ -170,14 +161,9 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
$db = $register->get('dbPool')->get(); $db = $register->get('dbPool')->get();
$redis = $register->get('redisPool')->get(); $redis = $register->get('redisPool')->get();
App::setResource('db', function () use (&$db) { App::setResource('db', fn() => $db);
return $db; App::setResource('cache', fn() => $redis);
});
App::setResource('cache', function () use (&$redis) {
return $redis;
});
try { try {
Authorization::cleanRoles(); Authorization::cleanRoles();
Authorization::setRole('role:all'); Authorization::setRole('role:all');

View file

@ -315,7 +315,7 @@ Structure::addFormat(APP_DATABASE_ATTRIBUTE_EMAIL, function() {
Structure::addFormat(APP_DATABASE_ATTRIBUTE_ENUM, function($attribute) { Structure::addFormat(APP_DATABASE_ATTRIBUTE_ENUM, function($attribute) {
$elements = $attribute['formatOptions']['elements']; $elements = $attribute['formatOptions']['elements'];
return new WhiteList($elements); return new WhiteList($elements, true);
}, Database::VAR_STRING); }, Database::VAR_STRING);
Structure::addFormat(APP_DATABASE_ATTRIBUTE_IP, function() { Structure::addFormat(APP_DATABASE_ATTRIBUTE_IP, function() {
@ -547,9 +547,7 @@ Locale::setLanguageFromJSON('zh-tw', __DIR__.'/config/locale/translations/zh-tw.
// Runtime Execution // Runtime Execution
App::setResource('register', function() use ($register) { App::setResource('register', fn() => $register);
return $register;
});
App::setResource('layout', function($locale) { App::setResource('layout', function($locale) {
$layout = new View(__DIR__.'/views/layouts/default.phtml'); $layout = new View(__DIR__.'/views/layouts/default.phtml');
@ -801,6 +799,12 @@ App::setResource('dbForConsole', function($db, $cache) {
App::setResource('mode', function($request) { App::setResource('mode', function($request) {
/** @var Utopia\Swoole\Request $request */ /** @var Utopia\Swoole\Request $request */
/**
* Defines the mode for the request:
* - 'default' => Requests for Client and Server Side
* - 'admin' => Request from the Console on non-console projects
*/
return $request->getParam('mode', $request->getHeader('x-appwrite-mode', APP_MODE_DEFAULT)); return $request->getParam('mode', $request->getHeader('x-appwrite-mode', APP_MODE_DEFAULT));
}, ['request']); }, ['request']);

View file

@ -87,9 +87,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
'timestamp' => time(), 'timestamp' => time(),
'value' => '{}' 'value' => '{}'
]); ]);
$statsDocument = Authorization::skip(function () use ($database, $document) { $statsDocument = Authorization::skip(fn() => $database->createDocument('realtime', $document));
return $database->createDocument('realtime', $document);
});
} catch (\Throwable $th) { } catch (\Throwable $th) {
Console::error('[Error] Type: ' . get_class($th)); Console::error('[Error] Type: ' . get_class($th));
Console::error('[Error] Message: ' . $th->getMessage()); Console::error('[Error] Message: ' . $th->getMessage());
@ -106,12 +104,8 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
Timer::tick(5000, function () use ($register, $stats, $containerId, &$statsDocument) { Timer::tick(5000, function () use ($register, $stats, $containerId, &$statsDocument) {
/** @var Document $statsDocument */ /** @var Document $statsDocument */
foreach ($stats as $projectId => $value) { foreach ($stats as $projectId => $value) {
if (empty($value['connections']) && empty($value['messages'])) { $connections = $stats->get($projectId, 'connections') ?? 0;
continue; $messages = $stats->get($projectId, 'messages' ?? 0);
}
$connections = $stats->get($projectId, 'connections');
$messages = $stats->get($projectId, 'messages');
$usage = new Event('v1-usage', 'UsageV1'); $usage = new Event('v1-usage', 'UsageV1');
$usage $usage
@ -132,9 +126,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
} }
$payload = []; $payload = [];
foreach ($stats as $projectId => $value) { foreach ($stats as $projectId => $value) {
if (!empty($value['connectionsTotal'])) { $payload[$projectId] = $stats->get($projectId, 'connectionsTotal');
$payload[$projectId] = $stats->get($projectId, 'connectionsTotal');
}
} }
if (empty($payload) || empty($statsDocument)) { if (empty($payload) || empty($statsDocument)) {
return; return;
@ -147,9 +139,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
->setAttribute('timestamp', time()) ->setAttribute('timestamp', time())
->setAttribute('value', json_encode($payload)); ->setAttribute('value', json_encode($payload));
Authorization::skip(function () use ($database, $statsDocument) { Authorization::skip(fn() => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument));
$database->updateDocument('realtime', $statsDocument->getId(), $statsDocument);
});
} catch (\Throwable $th) { } catch (\Throwable $th) {
Console::error('[Error] Type: ' . get_class($th)); Console::error('[Error] Type: ' . get_class($th));
Console::error('[Error] Message: ' . $th->getMessage()); Console::error('[Error] Message: ' . $th->getMessage());
@ -177,11 +167,9 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
$payload = []; $payload = [];
$list = Authorization::skip(function () use ($database) { $list = Authorization::skip(fn() => $database->find('realtime', [
return $database->find('realtime', [
new Query('timestamp', Query::TYPE_GREATER, [(time() - 15)]) new Query('timestamp', Query::TYPE_GREATER, [(time() - 15)])
]); ]));
});
/** /**
* Aggregate stats across containers. * Aggregate stats across containers.
@ -335,21 +323,10 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
Console::info("Connection open (user: {$connection})"); Console::info("Connection open (user: {$connection})");
App::setResource('db', function () use (&$db) { App::setResource('db', fn() => $db);
return $db; App::setResource('cache', fn() => $redis);
}); App::setResource('request', fn() => $request);
App::setResource('response', fn() => $response);
App::setResource('cache', function () use (&$redis) {
return $redis;
});
App::setResource('request', function () use ($request) {
return $request;
});
App::setResource('response', function () use ($response) {
return $response;
});
try { try {
/** @var \Utopia\Database\Document $user */ /** @var \Utopia\Database\Document $user */
@ -493,7 +470,7 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
} }
switch ($message['type']) { switch ($message['type']) {
/** /**
* This type is used to authenticate. * This type is used to authenticate.
*/ */
case 'authentication': case 'authentication':

View file

@ -221,7 +221,7 @@ $logs = $this->getParam('logs', null);
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" /> <input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{project-collection.$id}}" /> <input type="hidden" name="collectionId" data-ls-bind="{{project-collection.$id}}" />
<input type="hidden" name="attributeId" data-ls-bind="{{attribute.key}}" /> <input type="hidden" name="key" data-ls-bind="{{attribute.key}}" />
<button class="danger small">Delete</button> <button class="danger small">Delete</button>
</form> </form>
@ -275,7 +275,7 @@ $logs = $this->getParam('logs', null);
<tr> <tr>
<th width="30"></th> <th width="30"></th>
<th width="80"></th> <th width="80"></th>
<th width="130">Index ID</th> <th width="130">Index Key</th>
<th width="100">Type</th> <th width="100">Type</th>
<th width="180">Attributes</th> <th width="180">Attributes</th>
<th></th> <th></th>
@ -295,7 +295,7 @@ $logs = $this->getParam('logs', null);
<span data-ls-if="{{index.status}} == 'deleting'" class="text-size-small text-danger">deleting&nbsp;</span> <span data-ls-if="{{index.status}} == 'deleting'" class="text-size-small text-danger">deleting&nbsp;</span>
</td> </td>
<td data-title="Index ID: "> <td data-title="Index Key: ">
<span class="text-size-small" data-ls-bind="{{index.key}}"></span><span class="text-size-small" data-ls-if="{{index.size}}" data-ls-bind="({{index.size}})"></span> <span class="text-size-small" data-ls-bind="{{index.key}}"></span><span class="text-size-small" data-ls-if="{{index.size}}" data-ls-bind="({{index.size}})"></span>
</td> </td>
@ -334,7 +334,7 @@ $logs = $this->getParam('logs', null);
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" /> <input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{project-collection.$id}}" /> <input type="hidden" name="collectionId" data-ls-bind="{{project-collection.$id}}" />
<input type="hidden" name="indexId" data-ls-bind="{{index.key}}" /> <input type="hidden" name="key" data-ls-bind="{{index.key}}" />
<button class="danger small">Delete</button> <button class="danger small">Delete</button>
</form> </form>
@ -347,7 +347,7 @@ $logs = $this->getParam('logs', null);
<button class="new-index">Add Index</button> <button class="new-index">Add Index</button>
</li> </li>
<li data-state="/console/database/collection/activity?id={{router.params.id}}&project={{router.params.project}}"> <li data-state="/console/database/collection/activity?id={{router.params.id}}&project={{router.params.project}}">
<h2>Activity <span class="badge" data-ls-bind="{{logs.sum}}"></span></h2> <h2>Activity</h2>
<?php echo $logs->render(); ?> <?php echo $logs->render(); ?>
</li> </li>
@ -456,6 +456,9 @@ $logs = $this->getParam('logs', null);
<label for="collection-name">Name</label> <label for="collection-name">Name</label>
<input name="name" id="collection-name" type="text" autocomplete="off" data-ls-bind="{{project-collection.name}}" data-forms-text-direction required placeholder="Collection Name" maxlength="128" /> <input name="name" id="collection-name" type="text" autocomplete="off" data-ls-bind="{{project-collection.name}}" data-forms-text-direction required placeholder="Collection Name" maxlength="128" />
<div class="margin-bottom">
<input name="enabled" type="hidden" data-forms-switch data-cast-to="boolean" data-ls-bind="{{project-collection.enabled}}" /> &nbsp; Enabled <span class="tooltip" data-tooltip="Mark whether collection is enabled"><i class="icon-info-circled"></i></span>
</div>
<label class="margin-bottom-small">Permissions</label> <label class="margin-bottom-small">Permissions</label>
@ -467,8 +470,8 @@ $logs = $this->getParam('logs', null);
<div class="col span-1"><input name="permission" value="document" type="radio" class="margin-top-no" data-ls-bind="{{project-collection.permission}}" /></div> <div class="col span-1"><input name="permission" value="document" type="radio" class="margin-top-no" data-ls-bind="{{project-collection.permission}}" /></div>
<div class="col span-11"> <div class="col span-11">
<b>Document Level</b> <b>Document Level</b>
<p class="text-fade margin-top-tiny">With Document Level permissions, you have granular access control over every file. Users will only be able to access documents for which they have explicit permissions.</p> <p class="text-fade margin-top-tiny">With Document Level permissions, you have granular access control over every document. Users will only be able to access documents for which they have explicit permissions.</p>
<p class="text-fade margin-top-tiny">In this permission level, document permissions take precedence and bucket permissions are ignored.</p> <p class="text-fade margin-top-tiny">In this permission level, document permissions take precedence and collection permissions are ignored.</p>
</div> </div>
</div> </div>
@ -477,7 +480,7 @@ $logs = $this->getParam('logs', null);
<div class="col span-11"> <div class="col span-11">
<b>Collection Level</b> <b>Collection Level</b>
<p class="text-fade margin-top-tiny">With Collection Level permissions, you assign permissions only once in the collection.</p> <p class="text-fade margin-top-tiny">With Collection Level permissions, you assign permissions only once in the collection.</p>
<p class="text-fade margin-top-tiny">In this permission level, permissions assigned to collection takes the precedence and documents permissions are ignored</p> <p class="text-fade margin-top-tiny">In this permission level, permissions assigned to collection takes the precedence and documents permissions are ignored.</p>
<div data-ls-if="{{project-collection.permission}} === 'collection'"> <div data-ls-if="{{project-collection.permission}} === 'collection'">
<label for="collection-read">Read Access <span class="text-size-small">(<a data-ls-attrs="href={{env.HOME}}/docs/permissions" target="_blank" rel="noopener">Learn more</a>)</span></label> <label for="collection-read">Read Access <span class="text-size-small">(<a data-ls-attrs="href={{env.HOME}}/docs/permissions" target="_blank" rel="noopener">Learn more</a>)</span></label>
<input type="hidden" id="collection-read" name="read" data-forms-tags data-cast-to="json" data-ls-bind="{{project-collection.$read}}" placeholder="User ID, Team ID or Role" /> <input type="hidden" id="collection-read" name="read" data-forms-tags data-cast-to="json" data-ls-bind="{{project-collection.$read}}" placeholder="User ID, Team ID or Role" />
@ -560,8 +563,8 @@ $logs = $this->getParam('logs', null);
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" /> <input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" /> <input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="string-attributeId">Attribute ID</label> <label for="string-key">Attribute ID</label>
<input type="text" class="full-width" name="attributeId" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" /> <input id="string-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore</div> <div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore</div>
<label for="string-length">Size</label> <label for="string-length">Size</label>
@ -637,8 +640,8 @@ $logs = $this->getParam('logs', null);
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" /> <input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" /> <input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="integer-attributeId">Attribute ID</label> <label for="integer-key">Attribute ID</label>
<input id="integer-attributeId" type="text" class="full-width" name="attributeId" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" /> <input id="integer-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore</div> <div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore</div>
<div class="margin-bottom"> <div class="margin-bottom">
@ -719,8 +722,8 @@ $logs = $this->getParam('logs', null);
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" /> <input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" /> <input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="float-attributeId">Attribute ID</label> <label for="float-key">Attribute ID</label>
<input id="float-attributeId" type="text" class="full-width" name="attributeId" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" /> <input id="float-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore</div> <div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore</div>
<div class="margin-bottom"> <div class="margin-bottom">
@ -800,8 +803,8 @@ $logs = $this->getParam('logs', null);
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" /> <input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" /> <input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="email-attributeId">Attribute ID</label> <label for="email-key">Attribute ID</label>
<input id="email-attributeId" type="text" class="full-width" name="attributeId" required autocomplete="off" maxlength="128" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" /> <input id="email-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="128" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore</div> <div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore</div>
<div class="margin-bottom"> <div class="margin-bottom">
@ -870,8 +873,8 @@ $logs = $this->getParam('logs', null);
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" /> <input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" /> <input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="boolean-attributeId">Attribute ID</label> <label for="boolean-key">Attribute ID</label>
<input id="boolean-attributeId" type="text" class="full-width" name="attributeId" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" /> <input id="boolean-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore</div> <div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore</div>
<div class="margin-bottom"> <div class="margin-bottom">
@ -943,8 +946,8 @@ $logs = $this->getParam('logs', null);
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" /> <input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" /> <input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="ip-attributeId">Attribute ID</label> <label for="ip-key">Attribute ID</label>
<input id="ip-attributeId" type="text" class="full-width" name="attributeId" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" /> <input id="ip-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore</div> <div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore</div>
<div class="margin-bottom"> <div class="margin-bottom">
@ -1013,8 +1016,8 @@ $logs = $this->getParam('logs', null);
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" /> <input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" /> <input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="url-attributeId">Attribute ID</label> <label for="url-key">Attribute ID</label>
<input id="url-attributeId" type="text" class="full-width" name="attributeId" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" /> <input id="url-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore</div> <div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore</div>
<div class="margin-bottom"> <div class="margin-bottom">
@ -1083,8 +1086,8 @@ $logs = $this->getParam('logs', null);
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" /> <input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" /> <input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="enum-attributeId">Attribute ID</label> <label for="enum-key">Attribute ID</label>
<input id="enum-attributeId" type="text" class="full-width" name="attributeId" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" /> <input id="enum-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore</div> <div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore</div>
<label>Elements</label> <label>Elements</label>
@ -1180,8 +1183,8 @@ $logs = $this->getParam('logs', null);
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" /> <input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
<input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" /> <input type="hidden" name="collectionId" data-ls-bind="{{router.params.id}}" />
<label for="index-indexId">Index ID</label> <label for="index-key">Index Key</label>
<input id="index-indexId" type="text" class="full-width" name="indexId" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" /> <input id="index-key" type="text" class="full-width" name="key" required autocomplete="off" maxlength="36" pattern="^[a-zA-Z0-9][a-zA-Z0-9_-]{1,36}$" />
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore</div> <div class="text-fade text-size-xs margin-top-negative-small margin-bottom">Allowed Characters A-Z, a-z, 0-9, and non-leading underscore</div>
<label for="index-type">Type</label> <label for="index-type">Type</label>

View file

@ -481,16 +481,14 @@ class FunctionsV1 extends Worker
Console::info('Function executed in ' . ($executionEnd - $executionStart) . ' seconds, status: ' . $functionStatus); Console::info('Function executed in ' . ($executionEnd - $executionStart) . ' seconds, status: ' . $functionStatus);
$execution = Authorization::skip(function() use ($database, $execution, $tag, $functionStatus, $exitCode, $stdout, $stderr, $executionTime) { $execution = Authorization::skip(fn() => $database->updateDocument('executions', $execution->getId(), new Document(array_merge($execution->getArrayCopy(), [
return $database->updateDocument('executions', $execution->getId(), new Document(array_merge($execution->getArrayCopy(), [
'tagId' => $tag->getId(), 'tagId' => $tag->getId(),
'status' => $functionStatus, 'status' => $functionStatus,
'exitCode' => $exitCode, 'exitCode' => $exitCode,
'stdout' => \utf8_encode(\mb_substr($stdout, -8000)), // log last 8000 chars output 'stdout' => \utf8_encode(\mb_substr($stdout, -8000)), // log last 8000 chars output
'stderr' => \utf8_encode(\mb_substr($stderr, -8000)), // log last 8000 chars output 'stderr' => \utf8_encode(\mb_substr($stderr, -8000)), // log last 8000 chars output
'time' => (float)$executionTime, 'time' => (float)$executionTime,
]))); ]))));
});
$executionModel = new Execution(); $executionModel = new Execution();
$executionUpdate = new Event('v1-webhooks', 'WebhooksV1'); $executionUpdate = new Event('v1-webhooks', 'WebhooksV1');

12
composer.lock generated
View file

@ -2255,16 +2255,16 @@
}, },
{ {
"name": "utopia-php/framework", "name": "utopia-php/framework",
"version": "0.19.2", "version": "0.19.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/utopia-php/framework.git", "url": "https://github.com/utopia-php/framework.git",
"reference": "49e4374b97c0f4d14bc84b424bdc9c3b7747e15f" "reference": "4c6c841d738cec458b73fec5aedd40fd43bd41a7"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/utopia-php/framework/zipball/49e4374b97c0f4d14bc84b424bdc9c3b7747e15f", "url": "https://api.github.com/repos/utopia-php/framework/zipball/4c6c841d738cec458b73fec5aedd40fd43bd41a7",
"reference": "49e4374b97c0f4d14bc84b424bdc9c3b7747e15f", "reference": "4c6c841d738cec458b73fec5aedd40fd43bd41a7",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2298,9 +2298,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/utopia-php/framework/issues", "issues": "https://github.com/utopia-php/framework/issues",
"source": "https://github.com/utopia-php/framework/tree/0.19.2" "source": "https://github.com/utopia-php/framework/tree/0.19.3"
}, },
"time": "2021-12-07T09:29:35+00:00" "time": "2021-12-17T13:04:13+00:00"
}, },
{ {
"name": "utopia-php/image", "name": "utopia-php/image",

View file

@ -0,0 +1,14 @@
const sdk = new Appwrite();
sdk
.setEndpoint('https://[HOSTNAME_OR_IP]/v1') // Your API Endpoint
.setProject('5df5acd0d48c2') // Your project ID
;
let promise = sdk.functions.listRuntimes();
promise.then(function (response) {
console.log(response); // Success
}, function (error) {
console.log(error); // Failure
});

View file

@ -5,7 +5,7 @@ sdk
.setProject('5df5acd0d48c2') // Your project ID .setProject('5df5acd0d48c2') // Your project ID
; ;
let promise = sdk.projects.delete('[PROJECT_ID]', '[PASSWORD]'); let promise = sdk.projects.delete('[PROJECT_ID]', 'password');
promise.then(function (response) { promise.then(function (response) {
console.log(response); // Success console.log(response); // Success

View file

@ -1,5 +1,5 @@
Use this endpoint to invite a new member to join your team. If initiated from Client SDK, an email with a link to join the team will be sent to the new member's email address if the member doesn't exist in the project it will be created automatically. If initiated from server side SDKs, new member will automatically be added to the team. Invite a new member to join your team. If initiated from the client SDK, an email with a link to join the team will be sent to the member's email address and an account will be created for them should they not be signed up already. If initiated from server-side SDKs, the new member will automatically be added to the team.
Use the 'URL' parameter to redirect the user from the invitation email back to your app. When the user is redirected, use the [Update Team Membership Status](/docs/client/teams#teamsUpdateMembershipStatus) endpoint to allow the user to accept the invitation to the team. While calling from side SDKs the redirect url can be empty string. Use the 'url' parameter to redirect the user from the invitation email back to your app. When the user is redirected, use the [Update Team Membership Status](/docs/client/teams#teamsUpdateMembershipStatus) endpoint to allow the user to accept the invitation to the team.
Please note that in order to avoid a [Redirect Attacks](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URL's are the once from domains you have set when added your platforms in the console interface. Please note that to avoid a [Redirect Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URL's are the once from domains you have set when adding your platforms in the console interface.

View file

@ -1 +1 @@
Create a new team. The user who creates the team will automatically be assigned as the owner of the team. The team owner can invite new members, who will be able add new owners and update or delete the team from your project. Create a new team. The user who creates the team will automatically be assigned as the owner of the team. Only the users with the owner role can invite new members, add new owners and delete or update the team.

View file

@ -1 +1 @@
Delete a team by its unique ID. Only team owners have write access for this resource. Delete a team using its ID. Only team members with the owner role can delete the team.

View file

@ -1 +1 @@
Get a team members by the team unique ID. All team members have read access for this list of resources. Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint.

View file

@ -1 +1 @@
Get a team by its unique ID. All team members have read access for this resource. Get a team by its ID. All team members have read access for this resource.

View file

@ -1 +1,3 @@
Get a list of all the current user teams. You can use the query params to filter your results. On admin mode, this endpoint will return a list of all of the project's teams. [Learn more about different API modes](/docs/admin). Get a list of all the teams in which the current user is a member. You can use the parameters to filter your results.
In admin mode, this endpoint returns a list of all the teams in the current project. [Learn more about different API modes](/docs/admin).

View file

@ -0,0 +1 @@
Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](/docs/permissions).

View file

@ -1 +1 @@
Update a team by its unique ID. Only team owners have write access for this resource. Update a team using its ID. Only members with the owner role can update the team.

View file

@ -46,6 +46,23 @@ For **Mac OS** add your app name and Bundle ID, You can find your Bundle Identif
### Web ### Web
Appwrite 0.7, and the Appwrite Flutter SDK 0.3.0 have added support for Flutter Web. To build web apps that integrate with Appwrite successfully, all you have to do is add a web platform on your Appwrite project's dashboard and list the domain your website will use to allow communication to the Appwrite API. Appwrite 0.7, and the Appwrite Flutter SDK 0.3.0 have added support for Flutter Web. To build web apps that integrate with Appwrite successfully, all you have to do is add a web platform on your Appwrite project's dashboard and list the domain your website will use to allow communication to the Appwrite API.
For web in order to capture the OAuth2 callback URL and send it to the application using JavaScript `postMessage()`, you need to create an html file inside `./web` folder of your Flutter project. For example `auth.html` with the following content.
```html
<!DOCTYPE html>
<title>Authentication complete</title>
<p>Authentication is complete. If this does not happen automatically, please
close the window.
<script>
window.opener.postMessage({
flutter-web-auth: window.location.href
}, window.location.origin);
window.close();
</script>
```
Redirection URL passed to the authentication service must be the same as the URL on which the application is running (schema, host, port if necessary) and the path must point to created HTML file, /auth.html in this case. The callbackUrlScheme parameter of the authenticate() method does not take into account, so it is possible to use a schema for native platforms in the code.
#### Flutter Web Cross-Domain Communication & Cookies #### Flutter Web Cross-Domain Communication & Cookies
While running Flutter Web, make sure your Appwrite server and your Flutter client are using the same top-level domain and the same protocol (HTTP or HTTPS) to communicate. When trying to communicate between different domains or protocols, you may receive HTTP status error 401 because some modern browsers block cross-site or insecure cookies for enhanced privacy. In production, Appwrite allows you set multiple [custom-domains](https://appwrite.io/docs/custom-domains) for each project. While running Flutter Web, make sure your Appwrite server and your Flutter client are using the same top-level domain and the same protocol (HTTP or HTTPS) to communicate. When trying to communicate between different domains or protocols, you may receive HTTP status error 401 because some modern browsers block cross-site or insecure cookies for enhanced privacy. In production, Appwrite allows you set multiple [custom-domains](https://appwrite.io/docs/custom-domains) for each project.

View file

@ -199,7 +199,7 @@
* *
* Use this endpoint to allow a new user to register a new account in your * Use this endpoint to allow a new user to register a new account in your
* project. After the user registration completes successfully, you can use * project. After the user registration completes successfully, you can use
* the [/account/verification](/docs/client/account#accountCreateVerification) * the [/account/verfication](/docs/client/account#accountCreateVerification)
* route to start verifying the user email address. To allow the new user to * route to start verifying the user email address. To allow the new user to
* login to their new account, you need to create a new [account * login to their new account, you need to create a new [account
* session](/docs/client/account#accountCreateSession). * session](/docs/client/account#accountCreateSession).
@ -264,12 +264,14 @@
* Update Account Email * Update Account Email
* *
* Update currently logged in user account email address. After changing user * Update currently logged in user account email address. After changing user
* address, user confirmation status is being reset and a new confirmation * address, the user confirmation status will get reset. A new confirmation
* mail is sent. For security measures, user password is required to complete * email is not sent automatically however you can use the send confirmation
* this request. * email endpoint again to send the confirmation email. For security measures,
* user password is required to complete this request.
* This endpoint can also be used to convert an anonymous account to a normal * This endpoint can also be used to convert an anonymous account to a normal
* one, by passing an email address and a new password. * one, by passing an email address and a new password.
* *
*
* @param {string} email * @param {string} email
* @param {string} password * @param {string} password
* @throws {AppwriteException} * @throws {AppwriteException}
@ -1165,8 +1167,8 @@
* @param {string} collectionId * @param {string} collectionId
* @param {string} name * @param {string} name
* @param {string} permission * @param {string} permission
* @param {string} read * @param {string[]} read
* @param {string} write * @param {string[]} write
* @throws {AppwriteException} * @throws {AppwriteException}
* @returns {Promise} * @returns {Promise}
*/ */
@ -1237,12 +1239,13 @@
* @param {string} collectionId * @param {string} collectionId
* @param {string} name * @param {string} name
* @param {string} permission * @param {string} permission
* @param {string} read * @param {string[]} read
* @param {string} write * @param {string[]} write
* @param {boolean} enabled
* @throws {AppwriteException} * @throws {AppwriteException}
* @returns {Promise} * @returns {Promise}
*/ */
updateCollection: (collectionId, name, permission, read, write) => __awaiter(this, void 0, void 0, function* () { updateCollection: (collectionId, name, permission, read, write, enabled) => __awaiter(this, void 0, void 0, function* () {
if (typeof collectionId === 'undefined') { if (typeof collectionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "collectionId"'); throw new AppwriteException('Missing required parameter: "collectionId"');
} }
@ -1266,6 +1269,9 @@
if (typeof write !== 'undefined') { if (typeof write !== 'undefined') {
payload['write'] = write; payload['write'] = write;
} }
if (typeof enabled !== 'undefined') {
payload['enabled'] = enabled;
}
const uri = new URL(this.config.endpoint + path); const uri = new URL(this.config.endpoint + path);
return yield this.call('put', uri, { return yield this.call('put', uri, {
'content-type': 'application/json', 'content-type': 'application/json',
@ -1318,27 +1324,27 @@
* *
* *
* @param {string} collectionId * @param {string} collectionId
* @param {string} attributeId * @param {string} key
* @param {boolean} required * @param {boolean} required
* @param {boolean} xdefault * @param {boolean} xdefault
* @param {boolean} array * @param {boolean} array
* @throws {AppwriteException} * @throws {AppwriteException}
* @returns {Promise} * @returns {Promise}
*/ */
createBooleanAttribute: (collectionId, attributeId, required, xdefault, array) => __awaiter(this, void 0, void 0, function* () { createBooleanAttribute: (collectionId, key, required, xdefault, array) => __awaiter(this, void 0, void 0, function* () {
if (typeof collectionId === 'undefined') { if (typeof collectionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "collectionId"'); throw new AppwriteException('Missing required parameter: "collectionId"');
} }
if (typeof attributeId === 'undefined') { if (typeof key === 'undefined') {
throw new AppwriteException('Missing required parameter: "attributeId"'); throw new AppwriteException('Missing required parameter: "key"');
} }
if (typeof required === 'undefined') { if (typeof required === 'undefined') {
throw new AppwriteException('Missing required parameter: "required"'); throw new AppwriteException('Missing required parameter: "required"');
} }
let path = '/database/collections/{collectionId}/attributes/boolean'.replace('{collectionId}', collectionId); let path = '/database/collections/{collectionId}/attributes/boolean'.replace('{collectionId}', collectionId);
let payload = {}; let payload = {};
if (typeof attributeId !== 'undefined') { if (typeof key !== 'undefined') {
payload['attributeId'] = attributeId; payload['key'] = key;
} }
if (typeof required !== 'undefined') { if (typeof required !== 'undefined') {
payload['required'] = required; payload['required'] = required;
@ -1361,27 +1367,27 @@
* *
* *
* @param {string} collectionId * @param {string} collectionId
* @param {string} attributeId * @param {string} key
* @param {boolean} required * @param {boolean} required
* @param {string} xdefault * @param {string} xdefault
* @param {boolean} array * @param {boolean} array
* @throws {AppwriteException} * @throws {AppwriteException}
* @returns {Promise} * @returns {Promise}
*/ */
createEmailAttribute: (collectionId, attributeId, required, xdefault, array) => __awaiter(this, void 0, void 0, function* () { createEmailAttribute: (collectionId, key, required, xdefault, array) => __awaiter(this, void 0, void 0, function* () {
if (typeof collectionId === 'undefined') { if (typeof collectionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "collectionId"'); throw new AppwriteException('Missing required parameter: "collectionId"');
} }
if (typeof attributeId === 'undefined') { if (typeof key === 'undefined') {
throw new AppwriteException('Missing required parameter: "attributeId"'); throw new AppwriteException('Missing required parameter: "key"');
} }
if (typeof required === 'undefined') { if (typeof required === 'undefined') {
throw new AppwriteException('Missing required parameter: "required"'); throw new AppwriteException('Missing required parameter: "required"');
} }
let path = '/database/collections/{collectionId}/attributes/email'.replace('{collectionId}', collectionId); let path = '/database/collections/{collectionId}/attributes/email'.replace('{collectionId}', collectionId);
let payload = {}; let payload = {};
if (typeof attributeId !== 'undefined') { if (typeof key !== 'undefined') {
payload['attributeId'] = attributeId; payload['key'] = key;
} }
if (typeof required !== 'undefined') { if (typeof required !== 'undefined') {
payload['required'] = required; payload['required'] = required;
@ -1402,7 +1408,7 @@
* *
* *
* @param {string} collectionId * @param {string} collectionId
* @param {string} attributeId * @param {string} key
* @param {string[]} elements * @param {string[]} elements
* @param {boolean} required * @param {boolean} required
* @param {string} xdefault * @param {string} xdefault
@ -1410,12 +1416,12 @@
* @throws {AppwriteException} * @throws {AppwriteException}
* @returns {Promise} * @returns {Promise}
*/ */
createEnumAttribute: (collectionId, attributeId, elements, required, xdefault, array) => __awaiter(this, void 0, void 0, function* () { createEnumAttribute: (collectionId, key, elements, required, xdefault, array) => __awaiter(this, void 0, void 0, function* () {
if (typeof collectionId === 'undefined') { if (typeof collectionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "collectionId"'); throw new AppwriteException('Missing required parameter: "collectionId"');
} }
if (typeof attributeId === 'undefined') { if (typeof key === 'undefined') {
throw new AppwriteException('Missing required parameter: "attributeId"'); throw new AppwriteException('Missing required parameter: "key"');
} }
if (typeof elements === 'undefined') { if (typeof elements === 'undefined') {
throw new AppwriteException('Missing required parameter: "elements"'); throw new AppwriteException('Missing required parameter: "elements"');
@ -1425,8 +1431,8 @@
} }
let path = '/database/collections/{collectionId}/attributes/enum'.replace('{collectionId}', collectionId); let path = '/database/collections/{collectionId}/attributes/enum'.replace('{collectionId}', collectionId);
let payload = {}; let payload = {};
if (typeof attributeId !== 'undefined') { if (typeof key !== 'undefined') {
payload['attributeId'] = attributeId; payload['key'] = key;
} }
if (typeof elements !== 'undefined') { if (typeof elements !== 'undefined') {
payload['elements'] = elements; payload['elements'] = elements;
@ -1453,7 +1459,7 @@
* *
* *
* @param {string} collectionId * @param {string} collectionId
* @param {string} attributeId * @param {string} key
* @param {boolean} required * @param {boolean} required
* @param {string} min * @param {string} min
* @param {string} max * @param {string} max
@ -1462,20 +1468,20 @@
* @throws {AppwriteException} * @throws {AppwriteException}
* @returns {Promise} * @returns {Promise}
*/ */
createFloatAttribute: (collectionId, attributeId, required, min, max, xdefault, array) => __awaiter(this, void 0, void 0, function* () { createFloatAttribute: (collectionId, key, required, min, max, xdefault, array) => __awaiter(this, void 0, void 0, function* () {
if (typeof collectionId === 'undefined') { if (typeof collectionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "collectionId"'); throw new AppwriteException('Missing required parameter: "collectionId"');
} }
if (typeof attributeId === 'undefined') { if (typeof key === 'undefined') {
throw new AppwriteException('Missing required parameter: "attributeId"'); throw new AppwriteException('Missing required parameter: "key"');
} }
if (typeof required === 'undefined') { if (typeof required === 'undefined') {
throw new AppwriteException('Missing required parameter: "required"'); throw new AppwriteException('Missing required parameter: "required"');
} }
let path = '/database/collections/{collectionId}/attributes/float'.replace('{collectionId}', collectionId); let path = '/database/collections/{collectionId}/attributes/float'.replace('{collectionId}', collectionId);
let payload = {}; let payload = {};
if (typeof attributeId !== 'undefined') { if (typeof key !== 'undefined') {
payload['attributeId'] = attributeId; payload['key'] = key;
} }
if (typeof required !== 'undefined') { if (typeof required !== 'undefined') {
payload['required'] = required; payload['required'] = required;
@ -1505,7 +1511,7 @@
* *
* *
* @param {string} collectionId * @param {string} collectionId
* @param {string} attributeId * @param {string} key
* @param {boolean} required * @param {boolean} required
* @param {number} min * @param {number} min
* @param {number} max * @param {number} max
@ -1514,20 +1520,20 @@
* @throws {AppwriteException} * @throws {AppwriteException}
* @returns {Promise} * @returns {Promise}
*/ */
createIntegerAttribute: (collectionId, attributeId, required, min, max, xdefault, array) => __awaiter(this, void 0, void 0, function* () { createIntegerAttribute: (collectionId, key, required, min, max, xdefault, array) => __awaiter(this, void 0, void 0, function* () {
if (typeof collectionId === 'undefined') { if (typeof collectionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "collectionId"'); throw new AppwriteException('Missing required parameter: "collectionId"');
} }
if (typeof attributeId === 'undefined') { if (typeof key === 'undefined') {
throw new AppwriteException('Missing required parameter: "attributeId"'); throw new AppwriteException('Missing required parameter: "key"');
} }
if (typeof required === 'undefined') { if (typeof required === 'undefined') {
throw new AppwriteException('Missing required parameter: "required"'); throw new AppwriteException('Missing required parameter: "required"');
} }
let path = '/database/collections/{collectionId}/attributes/integer'.replace('{collectionId}', collectionId); let path = '/database/collections/{collectionId}/attributes/integer'.replace('{collectionId}', collectionId);
let payload = {}; let payload = {};
if (typeof attributeId !== 'undefined') { if (typeof key !== 'undefined') {
payload['attributeId'] = attributeId; payload['key'] = key;
} }
if (typeof required !== 'undefined') { if (typeof required !== 'undefined') {
payload['required'] = required; payload['required'] = required;
@ -1556,27 +1562,27 @@
* *
* *
* @param {string} collectionId * @param {string} collectionId
* @param {string} attributeId * @param {string} key
* @param {boolean} required * @param {boolean} required
* @param {string} xdefault * @param {string} xdefault
* @param {boolean} array * @param {boolean} array
* @throws {AppwriteException} * @throws {AppwriteException}
* @returns {Promise} * @returns {Promise}
*/ */
createIpAttribute: (collectionId, attributeId, required, xdefault, array) => __awaiter(this, void 0, void 0, function* () { createIpAttribute: (collectionId, key, required, xdefault, array) => __awaiter(this, void 0, void 0, function* () {
if (typeof collectionId === 'undefined') { if (typeof collectionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "collectionId"'); throw new AppwriteException('Missing required parameter: "collectionId"');
} }
if (typeof attributeId === 'undefined') { if (typeof key === 'undefined') {
throw new AppwriteException('Missing required parameter: "attributeId"'); throw new AppwriteException('Missing required parameter: "key"');
} }
if (typeof required === 'undefined') { if (typeof required === 'undefined') {
throw new AppwriteException('Missing required parameter: "required"'); throw new AppwriteException('Missing required parameter: "required"');
} }
let path = '/database/collections/{collectionId}/attributes/ip'.replace('{collectionId}', collectionId); let path = '/database/collections/{collectionId}/attributes/ip'.replace('{collectionId}', collectionId);
let payload = {}; let payload = {};
if (typeof attributeId !== 'undefined') { if (typeof key !== 'undefined') {
payload['attributeId'] = attributeId; payload['key'] = key;
} }
if (typeof required !== 'undefined') { if (typeof required !== 'undefined') {
payload['required'] = required; payload['required'] = required;
@ -1599,7 +1605,7 @@
* *
* *
* @param {string} collectionId * @param {string} collectionId
* @param {string} attributeId * @param {string} key
* @param {number} size * @param {number} size
* @param {boolean} required * @param {boolean} required
* @param {string} xdefault * @param {string} xdefault
@ -1607,12 +1613,12 @@
* @throws {AppwriteException} * @throws {AppwriteException}
* @returns {Promise} * @returns {Promise}
*/ */
createStringAttribute: (collectionId, attributeId, size, required, xdefault, array) => __awaiter(this, void 0, void 0, function* () { createStringAttribute: (collectionId, key, size, required, xdefault, array) => __awaiter(this, void 0, void 0, function* () {
if (typeof collectionId === 'undefined') { if (typeof collectionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "collectionId"'); throw new AppwriteException('Missing required parameter: "collectionId"');
} }
if (typeof attributeId === 'undefined') { if (typeof key === 'undefined') {
throw new AppwriteException('Missing required parameter: "attributeId"'); throw new AppwriteException('Missing required parameter: "key"');
} }
if (typeof size === 'undefined') { if (typeof size === 'undefined') {
throw new AppwriteException('Missing required parameter: "size"'); throw new AppwriteException('Missing required parameter: "size"');
@ -1622,8 +1628,8 @@
} }
let path = '/database/collections/{collectionId}/attributes/string'.replace('{collectionId}', collectionId); let path = '/database/collections/{collectionId}/attributes/string'.replace('{collectionId}', collectionId);
let payload = {}; let payload = {};
if (typeof attributeId !== 'undefined') { if (typeof key !== 'undefined') {
payload['attributeId'] = attributeId; payload['key'] = key;
} }
if (typeof size !== 'undefined') { if (typeof size !== 'undefined') {
payload['size'] = size; payload['size'] = size;
@ -1649,27 +1655,27 @@
* *
* *
* @param {string} collectionId * @param {string} collectionId
* @param {string} attributeId * @param {string} key
* @param {boolean} required * @param {boolean} required
* @param {string} xdefault * @param {string} xdefault
* @param {boolean} array * @param {boolean} array
* @throws {AppwriteException} * @throws {AppwriteException}
* @returns {Promise} * @returns {Promise}
*/ */
createUrlAttribute: (collectionId, attributeId, required, xdefault, array) => __awaiter(this, void 0, void 0, function* () { createUrlAttribute: (collectionId, key, required, xdefault, array) => __awaiter(this, void 0, void 0, function* () {
if (typeof collectionId === 'undefined') { if (typeof collectionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "collectionId"'); throw new AppwriteException('Missing required parameter: "collectionId"');
} }
if (typeof attributeId === 'undefined') { if (typeof key === 'undefined') {
throw new AppwriteException('Missing required parameter: "attributeId"'); throw new AppwriteException('Missing required parameter: "key"');
} }
if (typeof required === 'undefined') { if (typeof required === 'undefined') {
throw new AppwriteException('Missing required parameter: "required"'); throw new AppwriteException('Missing required parameter: "required"');
} }
let path = '/database/collections/{collectionId}/attributes/url'.replace('{collectionId}', collectionId); let path = '/database/collections/{collectionId}/attributes/url'.replace('{collectionId}', collectionId);
let payload = {}; let payload = {};
if (typeof attributeId !== 'undefined') { if (typeof key !== 'undefined') {
payload['attributeId'] = attributeId; payload['key'] = key;
} }
if (typeof required !== 'undefined') { if (typeof required !== 'undefined') {
payload['required'] = required; payload['required'] = required;
@ -1690,18 +1696,18 @@
* *
* *
* @param {string} collectionId * @param {string} collectionId
* @param {string} attributeId * @param {string} key
* @throws {AppwriteException} * @throws {AppwriteException}
* @returns {Promise} * @returns {Promise}
*/ */
getAttribute: (collectionId, attributeId) => __awaiter(this, void 0, void 0, function* () { getAttribute: (collectionId, key) => __awaiter(this, void 0, void 0, function* () {
if (typeof collectionId === 'undefined') { if (typeof collectionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "collectionId"'); throw new AppwriteException('Missing required parameter: "collectionId"');
} }
if (typeof attributeId === 'undefined') { if (typeof key === 'undefined') {
throw new AppwriteException('Missing required parameter: "attributeId"'); throw new AppwriteException('Missing required parameter: "key"');
} }
let path = '/database/collections/{collectionId}/attributes/{attributeId}'.replace('{collectionId}', collectionId).replace('{attributeId}', attributeId); let path = '/database/collections/{collectionId}/attributes/{key}'.replace('{collectionId}', collectionId).replace('{key}', key);
let payload = {}; let payload = {};
const uri = new URL(this.config.endpoint + path); const uri = new URL(this.config.endpoint + path);
return yield this.call('get', uri, { return yield this.call('get', uri, {
@ -1713,18 +1719,18 @@
* *
* *
* @param {string} collectionId * @param {string} collectionId
* @param {string} attributeId * @param {string} key
* @throws {AppwriteException} * @throws {AppwriteException}
* @returns {Promise} * @returns {Promise}
*/ */
deleteAttribute: (collectionId, attributeId) => __awaiter(this, void 0, void 0, function* () { deleteAttribute: (collectionId, key) => __awaiter(this, void 0, void 0, function* () {
if (typeof collectionId === 'undefined') { if (typeof collectionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "collectionId"'); throw new AppwriteException('Missing required parameter: "collectionId"');
} }
if (typeof attributeId === 'undefined') { if (typeof key === 'undefined') {
throw new AppwriteException('Missing required parameter: "attributeId"'); throw new AppwriteException('Missing required parameter: "key"');
} }
let path = '/database/collections/{collectionId}/attributes/{attributeId}'.replace('{collectionId}', collectionId).replace('{attributeId}', attributeId); let path = '/database/collections/{collectionId}/attributes/{key}'.replace('{collectionId}', collectionId).replace('{key}', key);
let payload = {}; let payload = {};
const uri = new URL(this.config.endpoint + path); const uri = new URL(this.config.endpoint + path);
return yield this.call('delete', uri, { return yield this.call('delete', uri, {
@ -1793,8 +1799,8 @@
* @param {string} collectionId * @param {string} collectionId
* @param {string} documentId * @param {string} documentId
* @param {object} data * @param {object} data
* @param {string} read * @param {string[]} read
* @param {string} write * @param {string[]} write
* @throws {AppwriteException} * @throws {AppwriteException}
* @returns {Promise} * @returns {Promise}
*/ */
@ -1861,8 +1867,8 @@
* @param {string} collectionId * @param {string} collectionId
* @param {string} documentId * @param {string} documentId
* @param {object} data * @param {object} data
* @param {string} read * @param {string[]} read
* @param {string} write * @param {string[]} write
* @throws {AppwriteException} * @throws {AppwriteException}
* @returns {Promise} * @returns {Promise}
*/ */
@ -1974,19 +1980,19 @@
* *
* *
* @param {string} collectionId * @param {string} collectionId
* @param {string} indexId * @param {string} key
* @param {string} type * @param {string} type
* @param {string[]} attributes * @param {string[]} attributes
* @param {string[]} orders * @param {string[]} orders
* @throws {AppwriteException} * @throws {AppwriteException}
* @returns {Promise} * @returns {Promise}
*/ */
createIndex: (collectionId, indexId, type, attributes, orders) => __awaiter(this, void 0, void 0, function* () { createIndex: (collectionId, key, type, attributes, orders) => __awaiter(this, void 0, void 0, function* () {
if (typeof collectionId === 'undefined') { if (typeof collectionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "collectionId"'); throw new AppwriteException('Missing required parameter: "collectionId"');
} }
if (typeof indexId === 'undefined') { if (typeof key === 'undefined') {
throw new AppwriteException('Missing required parameter: "indexId"'); throw new AppwriteException('Missing required parameter: "key"');
} }
if (typeof type === 'undefined') { if (typeof type === 'undefined') {
throw new AppwriteException('Missing required parameter: "type"'); throw new AppwriteException('Missing required parameter: "type"');
@ -1996,8 +2002,8 @@
} }
let path = '/database/collections/{collectionId}/indexes'.replace('{collectionId}', collectionId); let path = '/database/collections/{collectionId}/indexes'.replace('{collectionId}', collectionId);
let payload = {}; let payload = {};
if (typeof indexId !== 'undefined') { if (typeof key !== 'undefined') {
payload['indexId'] = indexId; payload['key'] = key;
} }
if (typeof type !== 'undefined') { if (typeof type !== 'undefined') {
payload['type'] = type; payload['type'] = type;
@ -2018,18 +2024,18 @@
* *
* *
* @param {string} collectionId * @param {string} collectionId
* @param {string} indexId * @param {string} key
* @throws {AppwriteException} * @throws {AppwriteException}
* @returns {Promise} * @returns {Promise}
*/ */
getIndex: (collectionId, indexId) => __awaiter(this, void 0, void 0, function* () { getIndex: (collectionId, key) => __awaiter(this, void 0, void 0, function* () {
if (typeof collectionId === 'undefined') { if (typeof collectionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "collectionId"'); throw new AppwriteException('Missing required parameter: "collectionId"');
} }
if (typeof indexId === 'undefined') { if (typeof key === 'undefined') {
throw new AppwriteException('Missing required parameter: "indexId"'); throw new AppwriteException('Missing required parameter: "key"');
} }
let path = '/database/collections/{collectionId}/indexes/{indexId}'.replace('{collectionId}', collectionId).replace('{indexId}', indexId); let path = '/database/collections/{collectionId}/indexes/{key}'.replace('{collectionId}', collectionId).replace('{key}', key);
let payload = {}; let payload = {};
const uri = new URL(this.config.endpoint + path); const uri = new URL(this.config.endpoint + path);
return yield this.call('get', uri, { return yield this.call('get', uri, {
@ -2041,18 +2047,18 @@
* *
* *
* @param {string} collectionId * @param {string} collectionId
* @param {string} indexId * @param {string} key
* @throws {AppwriteException} * @throws {AppwriteException}
* @returns {Promise} * @returns {Promise}
*/ */
deleteIndex: (collectionId, indexId) => __awaiter(this, void 0, void 0, function* () { deleteIndex: (collectionId, key) => __awaiter(this, void 0, void 0, function* () {
if (typeof collectionId === 'undefined') { if (typeof collectionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "collectionId"'); throw new AppwriteException('Missing required parameter: "collectionId"');
} }
if (typeof indexId === 'undefined') { if (typeof key === 'undefined') {
throw new AppwriteException('Missing required parameter: "indexId"'); throw new AppwriteException('Missing required parameter: "key"');
} }
let path = '/database/collections/{collectionId}/indexes/{indexId}'.replace('{collectionId}', collectionId).replace('{indexId}', indexId); let path = '/database/collections/{collectionId}/indexes/{key}'.replace('{collectionId}', collectionId).replace('{key}', key);
let payload = {}; let payload = {};
const uri = new URL(this.config.endpoint + path); const uri = new URL(this.config.endpoint + path);
return yield this.call('delete', uri, { return yield this.call('delete', uri, {
@ -2234,6 +2240,22 @@
'content-type': 'application/json', 'content-type': 'application/json',
}, payload); }, payload);
}), }),
/**
* List the currently active function runtimes.
*
* Get a list of all runtimes that are currently active in your project.
*
* @throws {AppwriteException}
* @returns {Promise}
*/
listRuntimes: () => __awaiter(this, void 0, void 0, function* () {
let path = '/functions/runtimes';
let payload = {};
const uri = new URL(this.config.endpoint + path);
return yield this.call('get', uri, {
'content-type': 'application/json',
}, payload);
}),
/** /**
* Get Function * Get Function
* *

View file

@ -38,6 +38,9 @@
datasets: [] datasets: []
}, },
options: { options: {
animation: {
duration: 0
},
responsive: true, responsive: true,
hover: { hover: {
mode: "nearest", mode: "nearest",

View file

@ -612,20 +612,6 @@
"code": 61751, "code": 61751,
"src": "fontawesome" "src": "fontawesome"
}, },
{
"uid": "00d86f3e46c3c3d768e7246eb0eadd7f",
"css": "discord",
"code": 59463,
"src": "custom_icons",
"selected": true,
"svg": {
"path": "M435 432.9C411.3 432.9 392.5 453.8 392.5 479.2S411.7 525.4 435 525.4C458.8 525.4 477.5 504.6 477.5 479.2 477.9 453.8 458.8 432.9 435 432.9ZM587.1 432.9C563.3 432.9 544.6 453.8 544.6 479.2S563.8 525.4 587.1 525.4C610.8 525.4 629.6 504.6 629.6 479.2S610.8 432.9 587.1 432.9ZM789.6 83.3H231.3C184.2 83.3 145.8 121.7 145.8 169.2V732.5C145.8 780 184.2 818.3 231.3 818.3H703.8L681.7 741.3 735 790.8 785.4 837.5 875 916.7V169.2C875 121.7 836.7 83.3 789.6 83.3ZM628.8 627.5S613.8 609.6 601.3 593.8C655.8 578.3 676.7 544.2 676.7 544.2 659.6 555.4 643.3 563.3 628.8 568.8 607.9 577.5 587.9 583.3 568.3 586.7 528.3 594.2 491.7 592.1 460.4 586.3 436.7 581.7 416.3 575 399.2 568.3 389.6 564.6 379.2 560 368.8 554.2 367.5 553.3 366.3 552.9 365 552.1 364.2 551.7 363.8 551.3 363.3 550.8 355.8 546.7 351.7 543.8 351.7 543.8S371.7 577.1 424.6 592.9C412.1 608.8 396.7 627.5 396.7 627.5 304.6 624.6 269.6 564.2 269.6 564.2 269.6 430 329.6 321.3 329.6 321.3 389.6 276.3 446.7 277.5 446.7 277.5L450.8 282.5C375.8 304.2 341.3 337.1 341.3 337.1S350.4 332.1 365.8 325C410.4 305.4 445.8 300 460.4 298.8 462.9 298.3 465 297.9 467.5 297.9 492.9 294.6 521.7 293.8 551.7 297.1 591.3 301.7 633.8 313.3 677.1 337.1 677.1 337.1 644.2 305.8 573.3 284.2L579.2 277.5S636.3 276.3 696.3 321.3C696.3 321.3 756.3 430 756.3 564.2 756.3 564.2 720.8 624.6 628.8 627.5Z",
"width": 1021
},
"search": [
"discord-logo-black"
]
},
{ {
"uid": "c2152732d525871cf35345955854f711", "uid": "c2152732d525871cf35345955854f711",
"css": "moon-inv", "css": "moon-inv",
@ -789,6 +775,54 @@
"css": "boolean", "css": "boolean",
"code": 61957, "code": 61957,
"src": "fontawesome" "src": "fontawesome"
},
{
"uid": "47a1f80457068fbeab69fdb83d7d0817",
"css": "youtube-play",
"code": 61802,
"src": "fontawesome"
},
{
"uid": "b6941a012434c6246e2ad74248b6453e",
"css": "discord",
"code": 59463,
"src": "custom_icons",
"selected": true,
"svg": {
"path": "M1006.7 168.6C940.3 138.1 869 115.6 794.5 102.8 793.2 102.5 791.8 103.2 791.1 104.4 781.9 120.7 771.8 142 764.7 158.7 684.6 146.7 604.9 146.7 526.4 158.7 519.3 141.6 508.7 120.7 499.5 104.4 498.8 103.2 497.5 102.6 496.1 102.8 421.7 115.6 350.4 138.1 284 168.6 283.4 168.8 282.9 169.3 282.6 169.8 147.4 371.7 110.4 568.6 128.6 763.1 128.6 764.1 129.2 765 129.9 765.5 219.1 831 305.4 870.8 390.2 897.1 391.6 897.5 393 897 393.9 895.9 413.9 868.5 431.8 839.7 447.1 809.3 448 807.5 447.2 805.4 445.3 804.7 417 794 390 780.9 364 766 361.9 764.8 361.8 761.8 363.7 760.4 369.1 756.3 374.6 752.1 379.8 747.8 380.8 747 382.1 746.8 383.2 747.3 553.8 825.2 738.5 825.2 907.1 747.3 908.2 746.8 909.5 746.9 910.5 747.7 915.7 752 921.1 756.3 926.7 760.4 928.5 761.8 928.4 764.8 926.4 766 900.4 781.1 873.4 794 845 804.7 843.2 805.4 842.3 807.5 843.2 809.3 858.9 839.6 876.8 868.5 896.5 895.9 897.3 897 898.8 897.5 900.1 897.1 985.3 870.8 1071.7 831 1160.8 765.5 1161.6 765 1162.1 764.1 1162.2 763.2 1183.9 538.3 1125.8 343 1008 169.8 1007.8 169.3 1007.3 168.8 1006.7 168.6ZM472.6 644.7C421.2 644.7 378.9 597.5 378.9 539.6 378.9 481.7 420.4 434.6 472.6 434.6 525.2 434.6 567.1 482.1 566.3 539.6 566.3 597.5 524.8 644.7 472.6 644.7ZM819 644.7C767.6 644.7 725.3 597.5 725.3 539.6 725.3 481.7 766.8 434.6 819 434.6 871.6 434.6 913.5 482.1 912.6 539.6 912.6 597.5 871.6 644.7 819 644.7Z",
"width": 1291
},
"search": [
"discord-logo-color"
]
},
{
"uid": "00d86f3e46c3c3d768e7246eb0eadd7f",
"css": "discord",
"code": 59463,
"src": "custom_icons",
"selected": false,
"svg": {
"path": "M435 432.9C411.3 432.9 392.5 453.8 392.5 479.2S411.7 525.4 435 525.4C458.8 525.4 477.5 504.6 477.5 479.2 477.9 453.8 458.8 432.9 435 432.9ZM587.1 432.9C563.3 432.9 544.6 453.8 544.6 479.2S563.8 525.4 587.1 525.4C610.8 525.4 629.6 504.6 629.6 479.2S610.8 432.9 587.1 432.9ZM789.6 83.3H231.3C184.2 83.3 145.8 121.7 145.8 169.2V732.5C145.8 780 184.2 818.3 231.3 818.3H703.8L681.7 741.3 735 790.8 785.4 837.5 875 916.7V169.2C875 121.7 836.7 83.3 789.6 83.3ZM628.8 627.5S613.8 609.6 601.3 593.8C655.8 578.3 676.7 544.2 676.7 544.2 659.6 555.4 643.3 563.3 628.8 568.8 607.9 577.5 587.9 583.3 568.3 586.7 528.3 594.2 491.7 592.1 460.4 586.3 436.7 581.7 416.3 575 399.2 568.3 389.6 564.6 379.2 560 368.8 554.2 367.5 553.3 366.3 552.9 365 552.1 364.2 551.7 363.8 551.3 363.3 550.8 355.8 546.7 351.7 543.8 351.7 543.8S371.7 577.1 424.6 592.9C412.1 608.8 396.7 627.5 396.7 627.5 304.6 624.6 269.6 564.2 269.6 564.2 269.6 430 329.6 321.3 329.6 321.3 389.6 276.3 446.7 277.5 446.7 277.5L450.8 282.5C375.8 304.2 341.3 337.1 341.3 337.1S350.4 332.1 365.8 325C410.4 305.4 445.8 300 460.4 298.8 462.9 298.3 465 297.9 467.5 297.9 492.9 294.6 521.7 293.8 551.7 297.1 591.3 301.7 633.8 313.3 677.1 337.1 677.1 337.1 644.2 305.8 573.3 284.2L579.2 277.5S636.3 276.3 696.3 321.3C696.3 321.3 756.3 430 756.3 564.2 756.3 564.2 720.8 624.6 628.8 627.5Z",
"width": 1021
},
"search": [
"discord-logo-black"
]
},
{
"uid": "097a1d40f8e785fbd260946461a6ea9c",
"css": "discord",
"code": 59463,
"src": "custom_icons",
"selected": false,
"svg": {
"path": "M1092.8 89.1C1010.5 51.3 922.3 23.5 830 7.6 828.4 7.2 826.7 8 825.8 9.6 814.5 29.7 801.9 56.1 793.1 76.8 693.9 61.9 595.2 61.9 498 76.8 489.2 55.6 476.2 29.7 464.8 9.6 463.9 8.1 462.2 7.3 460.5 7.6 368.3 23.4 280.1 51.2 197.8 89.1 197.1 89.4 196.5 89.9 196 90.5 28.7 340.6-17.2 584.4 5.3 825.3 5.4 826.5 6.1 827.6 7 828.3 117.4 909.4 224.4 958.6 329.4 991.3 331 991.8 332.8 991.2 333.9 989.8 358.7 955.9 380.9 920.1 399.8 882.5 401 880.3 399.9 877.7 397.6 876.8 362.5 863.5 329.1 847.3 296.9 828.8 294.4 827.3 294.1 823.7 296.5 822 303.3 816.9 310 811.6 316.5 806.3 317.7 805.3 319.3 805.1 320.7 805.7 531.9 902.2 760.6 902.2 969.4 805.7 970.8 805.1 972.4 805.3 973.6 806.2 980.1 811.6 986.9 816.9 993.7 822 996 823.7 995.9 827.3 993.3 828.8 961.2 847.6 927.7 863.5 892.6 876.8 890.3 877.6 889.3 880.3 890.4 882.5 909.8 920.1 931.9 955.8 956.3 989.7 957.3 991.2 959.1 991.8 960.8 991.3 1066.3 958.6 1173.3 909.4 1283.7 828.3 1284.6 827.6 1285.2 826.5 1285.4 825.3 1312.3 546.9 1240.3 305 1094.5 90.6 1094.1 89.9 1093.5 89.4 1092.8 89.1ZM431.4 678.6C367.8 678.6 315.4 620.2 315.4 548.5 315.4 476.8 366.8 418.4 431.4 418.4 496.5 418.4 548.4 477.3 547.4 548.5 547.4 620.2 496 678.6 431.4 678.6ZM860.3 678.6C796.7 678.6 744.3 620.2 744.3 548.5 744.3 476.8 795.7 418.4 860.3 418.4 925.5 418.4 977.4 477.3 976.3 548.5 976.3 620.2 925.5 678.6 860.3 678.6Z",
"width": 1291
},
"search": [
"discord-logo-color"
]
} }
] ]
} }

File diff suppressed because one or more lines are too long

View file

@ -240,7 +240,7 @@ class Realtime extends Adapter
* @param Document|null $project * @param Document|null $project
* @return array * @return array
*/ */
public static function fromPayload(string $event, Document $payload, Document $project = null): array public static function fromPayload(string $event, Document $payload, Document $project = null, Document $collection = null): array
{ {
$channels = []; $channels = [];
$roles = []; $roles = [];
@ -275,12 +275,6 @@ class Realtime extends Adapter
$channels[] = 'teams.' . $payload->getId(); $channels[] = 'teams.' . $payload->getId();
$roles = ['team:' . $payload->getId()]; $roles = ['team:' . $payload->getId()];
break;
case strpos($event, 'database.collections.') === 0:
$channels[] = 'collections';
$channels[] = 'collections.' . $payload->getId();
$roles = $payload->getRead();
break; break;
case strpos($event, 'database.attributes.') === 0: case strpos($event, 'database.attributes.') === 0:
case strpos($event, 'database.indexes.') === 0: case strpos($event, 'database.indexes.') === 0:
@ -290,10 +284,15 @@ class Realtime extends Adapter
break; break;
case strpos($event, 'database.documents.') === 0: case strpos($event, 'database.documents.') === 0:
if ($collection->isEmpty()) {
throw new \Exception('Collection need to be passed to to Realtime for Document events in the Database.');
}
$channels[] = 'documents'; $channels[] = 'documents';
$channels[] = 'collections.' . $payload->getAttribute('$collection') . '.documents'; $channels[] = 'collections.' . $payload->getAttribute('$collection') . '.documents';
$channels[] = 'documents.' . $payload->getId(); $channels[] = 'documents.' . $payload->getId();
$roles = $payload->getRead();
$roles = ($collection->getAttribute('permission') === 'collection') ? $collection->getRead() : $payload->getRead();
break; break;
case strpos($event, 'storage.buckets.') === 0: case strpos($event, 'storage.buckets.') === 0:

View file

@ -36,6 +36,12 @@ class Collection extends Model
'default' => '', 'default' => '',
'example' => 'My Collection', 'example' => 'My Collection',
]) ])
->addRule('enabled', [
'type' => self::TYPE_BOOLEAN,
'description' => 'Collection enabled.',
'default' => true,
'example' => false,
])
->addRule('permission', [ ->addRule('permission', [
'type' => self::TYPE_STRING, 'type' => self::TYPE_STRING,
'description' => 'Collection permission model. Possible values: `document` or `collection`', 'description' => 'Collection permission model. Possible values: `document` or `collection`',

View file

@ -19,8 +19,8 @@ trait DatabaseBase
]), [ ]), [
'collectionId' => 'unique()', 'collectionId' => 'unique()',
'name' => 'Movies', 'name' => 'Movies',
'read' => ['role:all'], 'read' => [],
'write' => ['role:all'], 'write' => [],
'permission' => 'document', 'permission' => 'document',
]); ]);
@ -30,6 +30,70 @@ trait DatabaseBase
return ['moviesId' => $movies['body']['$id']]; return ['moviesId' => $movies['body']['$id']];
} }
/**
* @depends testCreateCollection
*/
public function testDisableCollection(array $data): void
{
/**
* Test for SUCCESS
*/
$response = $this->client->call(Client::METHOD_PUT, '/database/collections/' . $data['moviesId'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'name' => 'Movies',
'enabled' => false,
'permission' => 'document',
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertFalse($response['body']['enabled']);
if ($this->getSide() === 'client') {
$responseCreateDocument = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['moviesId'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'documentId' => 'unique()',
'data' => [
'title' => 'Captain America',
],
'read' => ['user:'.$this->getUser()['$id']],
'write' => ['user:'.$this->getUser()['$id']],
]);
$responseListDocument = $this->client->call(Client::METHOD_GET, '/database/collections/' . $data['moviesId'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$responseGetDocument = $this->client->call(Client::METHOD_GET, '/database/collections/' . $data['moviesId'] . '/documents/someID', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals($responseCreateDocument['headers']['status-code'], 404);
$this->assertEquals($responseListDocument['headers']['status-code'], 404);
$this->assertEquals($responseGetDocument['headers']['status-code'], 404);
}
$response = $this->client->call(Client::METHOD_PUT, '/database/collections/' . $data['moviesId'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'name' => 'Movies',
'enabled' => true,
'permission' => 'document',
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertTrue($response['body']['enabled']);
}
/** /**
* @depends testCreateCollection * @depends testCreateCollection
*/ */
@ -40,7 +104,7 @@ trait DatabaseBase
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => 'title', 'key' => 'title',
'size' => 256, 'size' => 256,
'required' => true, 'required' => true,
]); ]);
@ -50,7 +114,7 @@ trait DatabaseBase
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => 'releaseYear', 'key' => 'releaseYear',
'required' => true, 'required' => true,
]); ]);
@ -59,7 +123,7 @@ trait DatabaseBase
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => 'actors', 'key' => 'actors',
'size' => 256, 'size' => 256,
'required' => false, 'required' => false,
'array' => true, 'array' => true,
@ -113,8 +177,8 @@ trait DatabaseBase
]), [ ]), [
'collectionId' => 'unique()', 'collectionId' => 'unique()',
'name' => 'Response Models', 'name' => 'Response Models',
'read' => ['role:all'], 'read' => [],
'write' => ['role:all'], 'write' => [],
'permission' => 'document', 'permission' => 'document',
]); ]);
@ -128,7 +192,7 @@ trait DatabaseBase
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => 'string', 'key' => 'string',
'size' => 16, 'size' => 16,
'required' => false, 'required' => false,
'default' => 'default', 'default' => 'default',
@ -139,7 +203,7 @@ trait DatabaseBase
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => 'email', 'key' => 'email',
'required' => false, 'required' => false,
'default' => 'default@example.com', 'default' => 'default@example.com',
]); ]);
@ -149,7 +213,7 @@ trait DatabaseBase
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => 'enum', 'key' => 'enum',
'elements' => ['yes', 'no', 'maybe'], 'elements' => ['yes', 'no', 'maybe'],
'required' => false, 'required' => false,
'default' => 'maybe', 'default' => 'maybe',
@ -160,7 +224,7 @@ trait DatabaseBase
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => 'ip', 'key' => 'ip',
'required' => false, 'required' => false,
'default' => '192.0.2.0', 'default' => '192.0.2.0',
]); ]);
@ -170,7 +234,7 @@ trait DatabaseBase
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => 'url', 'key' => 'url',
'required' => false, 'required' => false,
'default' => 'http://example.com', 'default' => 'http://example.com',
]); ]);
@ -180,7 +244,7 @@ trait DatabaseBase
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => 'integer', 'key' => 'integer',
'required' => false, 'required' => false,
'min' => 1, 'min' => 1,
'max' => 5, 'max' => 5,
@ -192,7 +256,7 @@ trait DatabaseBase
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => 'float', 'key' => 'float',
'required' => false, 'required' => false,
'min' => 1.5, 'min' => 1.5,
'max' => 5.5, 'max' => 5.5,
@ -204,7 +268,7 @@ trait DatabaseBase
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => 'boolean', 'key' => 'boolean',
'required' => false, 'required' => false,
'default' => true, 'default' => true,
]); ]);
@ -568,7 +632,7 @@ trait DatabaseBase
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => 'enum', 'key' => 'enum',
'elements' => ['yes', 'no', ''], 'elements' => ['yes', 'no', ''],
'required' => false, 'required' => false,
'default' => 'maybe', 'default' => 'maybe',
@ -590,7 +654,7 @@ trait DatabaseBase
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'indexId' => 'titleIndex', 'key' => 'titleIndex',
'type' => 'fulltext', 'type' => 'fulltext',
'attributes' => ['title'], 'attributes' => ['title'],
]); ]);
@ -606,7 +670,7 @@ trait DatabaseBase
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'indexId' => 'releaseYear', 'key' => 'releaseYear',
'type' => 'key', 'type' => 'key',
'attributes' => ['releaseYear'], 'attributes' => ['releaseYear'],
]); ]);
@ -1229,8 +1293,8 @@ trait DatabaseBase
]), [ ]), [
'collectionId' => 'unique()', 'collectionId' => 'unique()',
'name' => 'invalidDocumentStructure', 'name' => 'invalidDocumentStructure',
'read' => ['role:all'], 'read' => [],
'write' => ['role:all'], 'write' => [],
'permission' => 'document', 'permission' => 'document',
]); ]);
@ -1244,7 +1308,7 @@ trait DatabaseBase
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => 'email', 'key' => 'email',
'required' => false, 'required' => false,
]); ]);
@ -1253,7 +1317,7 @@ trait DatabaseBase
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => 'enum', 'key' => 'enum',
'elements' => ['yes', 'no', 'maybe'], 'elements' => ['yes', 'no', 'maybe'],
'required' => false, 'required' => false,
]); ]);
@ -1263,7 +1327,7 @@ trait DatabaseBase
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => 'ip', 'key' => 'ip',
'required' => false, 'required' => false,
]); ]);
@ -1272,7 +1336,7 @@ trait DatabaseBase
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => 'url', 'key' => 'url',
'size' => 256, 'size' => 256,
'required' => false, 'required' => false,
]); ]);
@ -1282,7 +1346,7 @@ trait DatabaseBase
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => 'range', 'key' => 'range',
'required' => false, 'required' => false,
'min' => 1, 'min' => 1,
'max' => 10, 'max' => 10,
@ -1294,7 +1358,7 @@ trait DatabaseBase
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => 'floatRange', 'key' => 'floatRange',
'required' => false, 'required' => false,
'min' => 1.1, 'min' => 1.1,
'max' => 1.4, 'max' => 1.4,
@ -1305,7 +1369,7 @@ trait DatabaseBase
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => 'probability', 'key' => 'probability',
'required' => false, 'required' => false,
'min' => 0, 'min' => 0,
'max' => 1, 'max' => 1,
@ -1316,7 +1380,7 @@ trait DatabaseBase
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => 'upperBound', 'key' => 'upperBound',
'required' => false, 'required' => false,
'max' => 10, 'max' => 10,
]); ]);
@ -1326,7 +1390,7 @@ trait DatabaseBase
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => 'lowerBound', 'key' => 'lowerBound',
'required' => false, 'required' => false,
'min' => 5, 'min' => 5,
]); ]);
@ -1339,7 +1403,7 @@ trait DatabaseBase
'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => 'invalidRange', 'key' => 'invalidRange',
'required' => false, 'required' => false,
'min' => 4, 'min' => 4,
'max' => 3, 'max' => 3,
@ -1349,12 +1413,42 @@ trait DatabaseBase
'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => 'defaultArray', 'key' => 'defaultArray',
'required' => false, 'required' => false,
'default' => 42, 'default' => 42,
'array' => true, 'array' => true,
]); ]);
$defaultRequired = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/attributes/integer', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'attributeId' => 'defaultRequired',
'required' => true,
'default' => 12
]);
$enumDefault = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/attributes/enum', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'attributeId' => 'enumDefault',
'elements' => ['north', 'west'],
'default' => 'south'
]);
$enumDefaultStrict = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/attributes/enum', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'attributeId' => 'enumDefault',
'elements' => ['north', 'west'],
'default' => 'NORTH'
]);
$this->assertEquals(201, $email['headers']['status-code']); $this->assertEquals(201, $email['headers']['status-code']);
$this->assertEquals(201, $ip['headers']['status-code']); $this->assertEquals(201, $ip['headers']['status-code']);
$this->assertEquals(201, $url['headers']['status-code']); $this->assertEquals(201, $url['headers']['status-code']);
@ -1363,8 +1457,12 @@ trait DatabaseBase
$this->assertEquals(201, $probability['headers']['status-code']); $this->assertEquals(201, $probability['headers']['status-code']);
$this->assertEquals(201, $upperBound['headers']['status-code']); $this->assertEquals(201, $upperBound['headers']['status-code']);
$this->assertEquals(201, $lowerBound['headers']['status-code']); $this->assertEquals(201, $lowerBound['headers']['status-code']);
$this->assertEquals(201, $enum['headers']['status-code']);
$this->assertEquals(400, $invalidRange['headers']['status-code']); $this->assertEquals(400, $invalidRange['headers']['status-code']);
$this->assertEquals(400, $defaultArray['headers']['status-code']); $this->assertEquals(400, $defaultArray['headers']['status-code']);
$this->assertEquals(400, $defaultRequired['headers']['status-code']);
$this->assertEquals(400, $enumDefault['headers']['status-code']);
$this->assertEquals(400, $enumDefaultStrict['headers']['status-code']);
$this->assertEquals('Minimum value must be lesser than maximum value', $invalidRange['body']['message']); $this->assertEquals('Minimum value must be lesser than maximum value', $invalidRange['body']['message']);
$this->assertEquals('Cannot set default value for array attributes', $defaultArray['body']['message']); $this->assertEquals('Cannot set default value for array attributes', $defaultArray['body']['message']);
@ -1788,7 +1886,7 @@ trait DatabaseBase
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => 'attribute', 'key' => 'attribute',
'size' => 64, 'size' => 64,
'required' => true, 'required' => true,
]); ]);
@ -1804,7 +1902,7 @@ trait DatabaseBase
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'indexId' => 'key_attribute', 'key' => 'key_attribute',
'type' => 'key', 'type' => 'key',
'attributes' => [$attribute['body']['key']], 'attributes' => [$attribute['body']['key']],
]); ]);
@ -1829,13 +1927,41 @@ trait DatabaseBase
$this->assertEquals(201, $document1['headers']['status-code']); $this->assertEquals(201, $document1['headers']['status-code']);
$document2 = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'documentId' => 'unique()',
'data' => [
'attribute' => 'one',
],
'read' => [],
'write' => [$user],
]);
$this->assertEquals(201, $document2['headers']['status-code']);
$document3 = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'documentId' => 'unique()',
'data' => [
'attribute' => 'one',
],
'read' => [],
'write' => [],
]);
$this->assertEquals(201, $document3['headers']['status-code']);
$documents = $this->client->call(Client::METHOD_GET, '/database/collections/' . $collectionId . '/documents', array_merge([ $documents = $this->client->call(Client::METHOD_GET, '/database/collections/' . $collectionId . '/documents', array_merge([
'content-type' => 'application/json', 'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders())); ], $this->getHeaders()));
$this->assertEquals(1, $documents['body']['sum']); $this->assertEquals(3, $documents['body']['sum']);
$this->assertCount(1, $documents['body']['documents']); $this->assertCount(3, $documents['body']['documents']);
/* /*
* Test for Failure * Test for Failure
@ -1894,7 +2020,7 @@ trait DatabaseBase
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
])); ]));
$this->assertEquals(404, $documents['headers']['status-code']); $this->assertEquals(401, $documents['headers']['status-code']);
} }
/** /**
@ -1907,7 +2033,7 @@ trait DatabaseBase
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'indexId' => 'unique_title', 'key' => 'unique_title',
'type' => 'unique', 'type' => 'unique',
'attributes' => ['title'], 'attributes' => ['title'],
]); ]);

View file

@ -165,7 +165,7 @@ class DatabaseCustomServerTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => 'firstName', 'key' => 'firstName',
'size' => 256, 'size' => 256,
'required' => true, 'required' => true,
]); ]);
@ -175,7 +175,7 @@ class DatabaseCustomServerTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => 'lastName', 'key' => 'lastName',
'size' => 256, 'size' => 256,
'required' => true, 'required' => true,
]); ]);
@ -185,7 +185,7 @@ class DatabaseCustomServerTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => 'unneeded', 'key' => 'unneeded',
'size' => 256, 'size' => 256,
'required' => true, 'required' => true,
]); ]);
@ -214,7 +214,7 @@ class DatabaseCustomServerTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'indexId' => 'key_lastName', 'key' => 'key_lastName',
'type' => 'key', 'type' => 'key',
'attributes' => [ 'attributes' => [
'lastName', 'lastName',
@ -275,7 +275,7 @@ class DatabaseCustomServerTest extends Scope
return [ return [
'collectionId' => $actors['body']['$id'], 'collectionId' => $actors['body']['$id'],
'indexId' => $index['body']['key'], 'key' => $index['body']['key'],
]; ];
} }
@ -284,7 +284,7 @@ class DatabaseCustomServerTest extends Scope
*/ */
public function testDeleteIndex($data): array public function testDeleteIndex($data): array
{ {
$index = $this->client->call(Client::METHOD_DELETE, '/database/collections/' . $data['collectionId'] . '/indexes/'. $data['indexId'], array_merge([ $index = $this->client->call(Client::METHOD_DELETE, '/database/collections/' . $data['collectionId'] . '/indexes/'. $data['key'], array_merge([
'content-type' => 'application/json', 'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
@ -316,7 +316,7 @@ class DatabaseCustomServerTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => 'attribute1', 'key' => 'attribute1',
'size' => 16, 'size' => 16,
'required' => true, 'required' => true,
]); ]);
@ -326,7 +326,7 @@ class DatabaseCustomServerTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => 'attribute2', 'key' => 'attribute2',
'size' => 16, 'size' => 16,
'required' => true, 'required' => true,
]); ]);
@ -343,7 +343,7 @@ class DatabaseCustomServerTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'indexId' => 'index1', 'key' => 'index1',
'type' => 'key', 'type' => 'key',
'attributes' => ['attribute1', 'attribute2'], 'attributes' => ['attribute1', 'attribute2'],
'orders' => ['ASC', 'ASC'], 'orders' => ['ASC', 'ASC'],
@ -354,7 +354,7 @@ class DatabaseCustomServerTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'indexId' => 'index2', 'key' => 'index2',
'type' => 'key', 'type' => 'key',
'attributes' => ['attribute2'], 'attributes' => ['attribute2'],
]); ]);
@ -428,7 +428,7 @@ class DatabaseCustomServerTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => 'attribute1', 'key' => 'attribute1',
'size' => 16, 'size' => 16,
'required' => true, 'required' => true,
]); ]);
@ -438,7 +438,7 @@ class DatabaseCustomServerTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => 'attribute2', 'key' => 'attribute2',
'size' => 16, 'size' => 16,
'required' => true, 'required' => true,
]); ]);
@ -455,7 +455,7 @@ class DatabaseCustomServerTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'indexId' => 'index1', 'key' => 'index1',
'type' => 'key', 'type' => 'key',
'attributes' => ['attribute1', 'attribute2'], 'attributes' => ['attribute1', 'attribute2'],
'orders' => ['ASC', 'ASC'], 'orders' => ['ASC', 'ASC'],
@ -466,7 +466,7 @@ class DatabaseCustomServerTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'indexId' => 'index2', 'key' => 'index2',
'type' => 'key', 'type' => 'key',
'attributes' => ['attribute2'], 'attributes' => ['attribute2'],
]); ]);
@ -615,7 +615,7 @@ class DatabaseCustomServerTest extends Scope
// 'x-appwrite-project' => $this->getProject()['$id'], // 'x-appwrite-project' => $this->getProject()['$id'],
// 'x-appwrite-key' => $this->getProject()['apiKey'] // 'x-appwrite-key' => $this->getProject()['apiKey']
// ]), [ // ]), [
// 'attributeId' => "attribute{$i}", // 'key' => "attribute{$i}",
// 'required' => false, // 'required' => false,
// ]); // ]);
@ -629,7 +629,7 @@ class DatabaseCustomServerTest extends Scope
// 'x-appwrite-project' => $this->getProject()['$id'], // 'x-appwrite-project' => $this->getProject()['$id'],
// 'x-appwrite-key' => $this->getProject()['apiKey'] // 'x-appwrite-key' => $this->getProject()['apiKey']
// ]), [ // ]), [
// 'attributeId' => "tooMany", // 'key' => "tooMany",
// 'required' => false, // 'required' => false,
// ]); // ]);
@ -663,7 +663,7 @@ class DatabaseCustomServerTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => "attribute{$i}", 'key' => "attribute{$i}",
'size' => 1024, 'size' => 1024,
'required' => true, 'required' => true,
]); ]);
@ -678,7 +678,7 @@ class DatabaseCustomServerTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => 'tooWide', 'key' => 'tooWide',
'size' => 1024, 'size' => 1024,
'required' => true, 'required' => true,
]); ]);
@ -714,7 +714,7 @@ class DatabaseCustomServerTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => "attribute{$i}", 'key' => "attribute{$i}",
'size' => 64, 'size' => 64,
'required' => true, 'required' => true,
]); ]);
@ -751,7 +751,7 @@ class DatabaseCustomServerTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'indexId' => "key_attribute{$i}", 'key' => "key_attribute{$i}",
'type' => 'key', 'type' => 'key',
'attributes' => ["attribute{$i}"], 'attributes' => ["attribute{$i}"],
]); ]);
@ -780,7 +780,7 @@ class DatabaseCustomServerTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'indexId' => 'tooMany', 'key' => 'tooMany',
'type' => 'key', 'type' => 'key',
'attributes' => ['attribute61'], 'attributes' => ['attribute61'],
]); ]);

View file

@ -26,7 +26,7 @@ class DatabasePermissionsGuestTest extends Scope
$collection = ['id' => $movies['body']['$id']]; $collection = ['id' => $movies['body']['$id']];
$this->client->call(Client::METHOD_POST, '/database/collections/' . $collection['id'] . '/attributes/string', $this->getServerHeader(), [ $this->client->call(Client::METHOD_POST, '/database/collections/' . $collection['id'] . '/attributes/string', $this->getServerHeader(), [
'attributeId' => 'title', 'key' => 'title',
'size' => 256, 'size' => 256,
'required' => true, 'required' => true,
]); ]);

View file

@ -65,7 +65,7 @@ class DatabasePermissionsMemberTest extends Scope
$this->collections = ['public' => $public['body']['$id']]; $this->collections = ['public' => $public['body']['$id']];
$response = $this->client->call(Client::METHOD_POST, '/database/collections/' . $this->collections['public'] . '/attributes/string', $this->getServerHeader(), [ $response = $this->client->call(Client::METHOD_POST, '/database/collections/' . $this->collections['public'] . '/attributes/string', $this->getServerHeader(), [
'attributeId' => 'title', 'key' => 'title',
'size' => 256, 'size' => 256,
'required' => true, 'required' => true,
]); ]);
@ -83,7 +83,7 @@ class DatabasePermissionsMemberTest extends Scope
$this->collections['private'] = $private['body']['$id']; $this->collections['private'] = $private['body']['$id'];
$this->client->call(Client::METHOD_POST, '/database/collections/' . $this->collections['private'] . '/attributes/string', $this->getServerHeader(), [ $this->client->call(Client::METHOD_POST, '/database/collections/' . $this->collections['private'] . '/attributes/string', $this->getServerHeader(), [
'attributeId' => 'title', 'key' => 'title',
'size' => 256, 'size' => 256,
'required' => true, 'required' => true,
]); ]);

View file

@ -45,7 +45,7 @@ class DatabasePermissionsTeamTest extends Scope
$this->collections['collection1'] = $collection1['body']['$id']; $this->collections['collection1'] = $collection1['body']['$id'];
$this->client->call(Client::METHOD_POST, '/database/collections/' . $this->collections['collection1'] . '/attributes/string', $this->getServerHeader(), [ $this->client->call(Client::METHOD_POST, '/database/collections/' . $this->collections['collection1'] . '/attributes/string', $this->getServerHeader(), [
'attributeId' => 'title', 'key' => 'title',
'size' => 256, 'size' => 256,
'required' => true, 'required' => true,
]); ]);
@ -61,7 +61,7 @@ class DatabasePermissionsTeamTest extends Scope
$this->collections['collection2'] = $collection2['body']['$id']; $this->collections['collection2'] = $collection2['body']['$id'];
$this->client->call(Client::METHOD_POST, '/database/collections/' . $this->collections['collection2'] . '/attributes/string', $this->getServerHeader(), [ $this->client->call(Client::METHOD_POST, '/database/collections/' . $this->collections['collection2'] . '/attributes/string', $this->getServerHeader(), [
'attributeId' => 'title', 'key' => 'title',
'size' => 256, 'size' => 256,
'required' => true, 'required' => true,
]); ]);
@ -160,7 +160,7 @@ class DatabasePermissionsTeamTest extends Scope
if ($success) { if ($success) {
$this->assertCount(1, $documents['body']['documents']); $this->assertCount(1, $documents['body']['documents']);
} else { } else {
$this->assertEquals(404, $documents['headers']['status-code']); $this->assertEquals(401, $documents['headers']['status-code']);
} }
} }

View file

@ -61,7 +61,7 @@ class RealtimeConsoleClientTest extends Scope
'content-type' => 'application/json', 'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [ ], $this->getHeaders()), [
'attributeId' => 'name', 'key' => 'name',
'size' => 256, 'size' => 256,
'required' => true, 'required' => true,
]); ]);
@ -134,7 +134,7 @@ class RealtimeConsoleClientTest extends Scope
'content-type' => 'application/json', 'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [ ], $this->getHeaders()), [
'indexId' => 'key_name', 'key' => 'key_name',
'type' => 'key', 'type' => 'key',
'attributes' => [ 'attributes' => [
'name', 'name',

View file

@ -77,9 +77,7 @@ class RealtimeCustomClientTest extends Scope
'files', 'files',
'files.1', 'files.1',
'collections', 'collections',
'collections.1',
'collections.1.documents', 'collections.1.documents',
'collections.2',
'collections.2.documents', 'collections.2.documents',
'documents', 'documents',
'documents.1', 'documents.1',
@ -93,15 +91,13 @@ class RealtimeCustomClientTest extends Scope
$this->assertEquals('connected', $response['type']); $this->assertEquals('connected', $response['type']);
$this->assertNotEmpty($response['data']); $this->assertNotEmpty($response['data']);
$this->assertNotEmpty($response['data']['user']); $this->assertNotEmpty($response['data']['user']);
$this->assertCount(12, $response['data']['channels']); $this->assertCount(10, $response['data']['channels']);
$this->assertContains('account', $response['data']['channels']); $this->assertContains('account', $response['data']['channels']);
$this->assertContains('account.' . $userId, $response['data']['channels']); $this->assertContains('account.' . $userId, $response['data']['channels']);
$this->assertContains('files', $response['data']['channels']); $this->assertContains('files', $response['data']['channels']);
$this->assertContains('files.1', $response['data']['channels']); $this->assertContains('files.1', $response['data']['channels']);
$this->assertContains('collections', $response['data']['channels']); $this->assertContains('collections', $response['data']['channels']);
$this->assertContains('collections.1', $response['data']['channels']);
$this->assertContains('collections.1.documents', $response['data']['channels']); $this->assertContains('collections.1.documents', $response['data']['channels']);
$this->assertContains('collections.2', $response['data']['channels']);
$this->assertContains('collections.2.documents', $response['data']['channels']); $this->assertContains('collections.2.documents', $response['data']['channels']);
$this->assertContains('documents', $response['data']['channels']); $this->assertContains('documents', $response['data']['channels']);
$this->assertContains('documents.1', $response['data']['channels']); $this->assertContains('documents.1', $response['data']['channels']);
@ -561,24 +557,11 @@ class RealtimeCustomClientTest extends Scope
]), [ ]), [
'collectionId' => 'unique()', 'collectionId' => 'unique()',
'name' => 'Actors', 'name' => 'Actors',
'read' => ['role:all'], 'read' => [],
'write' => ['role:all'], 'write' => [],
'permission' => 'collection' 'permission' => 'document'
]); ]);
$response = json_decode($client->receive(), true);
$this->assertArrayHasKey('type', $response);
$this->assertArrayHasKey('data', $response);
$this->assertEquals('event', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertArrayHasKey('timestamp', $response['data']);
$this->assertCount(2, $response['data']['channels']);
$this->assertContains('collections', $response['data']['channels']);
$this->assertContains('collections.' . $actors['body']['$id'], $response['data']['channels']);
$this->assertEquals('database.collections.create', $response['data']['event']);
$this->assertNotEmpty($response['data']['payload']);
$data = ['actorsId' => $actors['body']['$id']]; $data = ['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/' . $data['actorsId'] . '/attributes/string', array_merge([
@ -586,7 +569,7 @@ class RealtimeCustomClientTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => 'name', 'key' => 'name',
'size' => 256, 'size' => 256,
'required' => true, 'required' => true,
]); ]);
@ -662,7 +645,6 @@ class RealtimeCustomClientTest extends Scope
$this->assertEquals($response['data']['payload']['name'], 'Chris Evans 2'); $this->assertEquals($response['data']['payload']['name'], 'Chris Evans 2');
/** /**
* Test Document Delete * Test Document Delete
*/ */
@ -703,7 +685,167 @@ class RealtimeCustomClientTest extends Scope
$client->close(); $client->close();
} }
public function testChannelStorage() public function testChannelDatabaseCollectionPermissions()
{
$user = $this->getUser();
$session = $user['session'] ?? '';
$projectId = $this->getProject()['$id'];
$client = $this->getWebsocket(['documents', 'collections'], [
'origin' => 'http://localhost',
'cookie' => 'a_session_'.$projectId.'=' . $session
]);
$response = json_decode($client->receive(), true);
$this->assertArrayHasKey('type', $response);
$this->assertArrayHasKey('data', $response);
$this->assertEquals('connected', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertCount(2, $response['data']['channels']);
$this->assertContains('documents', $response['data']['channels']);
$this->assertContains('collections', $response['data']['channels']);
$this->assertNotEmpty($response['data']['user']);
$this->assertEquals($user['$id'], $response['data']['user']['$id']);
/**
* Test Collection Create
*/
$actors = $this->client->call(Client::METHOD_POST, '/database/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'collectionId' => 'unique()',
'name' => 'Actors',
'read' => ['role:all'],
'write' => ['role:all'],
'permission' => 'collection'
]);
$data = ['actorsId' => $actors['body']['$id']];
$name = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/attributes/string', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'name',
'size' => 256,
'required' => true,
]);
$this->assertEquals($name['headers']['status-code'], 201);
$this->assertEquals($name['body']['key'], 'name');
$this->assertEquals($name['body']['type'], 'string');
$this->assertEquals($name['body']['size'], 256);
$this->assertEquals($name['body']['required'], true);
sleep(2);
/**
* Test Document Create
*/
$document = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'documentId' => 'unique()',
'data' => [
'name' => 'Chris Evans'
],
'read' => [],
'write' => [],
]);
$response = json_decode($client->receive(), true);
$this->assertArrayHasKey('type', $response);
$this->assertArrayHasKey('data', $response);
$this->assertEquals('event', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertArrayHasKey('timestamp', $response['data']);
$this->assertCount(3, $response['data']['channels']);
$this->assertContains('documents', $response['data']['channels']);
$this->assertContains('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->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([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => [
'name' => 'Chris Evans 2'
],
'read' => [],
'write' => [],
]);
$response = json_decode($client->receive(), true);
$this->assertArrayHasKey('type', $response);
$this->assertArrayHasKey('data', $response);
$this->assertEquals('event', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertArrayHasKey('timestamp', $response['data']);
$this->assertCount(3, $response['data']['channels']);
$this->assertContains('documents', $response['data']['channels']);
$this->assertContains('documents.' . $data['documentId'], $response['data']['channels']);
$this->assertContains('collections.' . $data['actorsId'] . '.documents', $response['data']['channels']);
$this->assertEquals('database.documents.update', $response['data']['event']);
$this->assertNotEmpty($response['data']['payload']);
$this->assertEquals($response['data']['payload']['name'], 'Chris Evans 2');
/**
* Test Document Delete
*/
$document = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'documentId' => 'unique()',
'data' => [
'name' => 'Bradley Cooper'
],
'read' => [],
'write' => [],
]);
$client->receive();
$this->client->call(Client::METHOD_DELETE, '/database/collections/' . $data['actorsId'] . '/documents/' . $document['body']['$id'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$response = json_decode($client->receive(), true);
$this->assertArrayHasKey('type', $response);
$this->assertArrayHasKey('data', $response);
$this->assertEquals('event', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertArrayHasKey('timestamp', $response['data']);
$this->assertCount(3, $response['data']['channels']);
$this->assertContains('documents', $response['data']['channels']);
$this->assertContains('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->assertNotEmpty($response['data']['payload']);
$this->assertEquals($response['data']['payload']['name'], 'Bradley Cooper');
$client->close();
}
public function testChannelFiles()
{ {
$user = $this->getUser(); $user = $this->getUser();
$session = $user['session'] ?? ''; $session = $user['session'] ?? '';

View file

@ -57,7 +57,7 @@ trait WebhooksBase
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => 'firstName', 'key' => 'firstName',
'size' => 256, 'size' => 256,
'required' => true, 'required' => true,
]); ]);
@ -67,7 +67,7 @@ trait WebhooksBase
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => 'lastName', 'key' => 'lastName',
'size' => 256, 'size' => 256,
'required' => true, 'required' => true,
]); ]);
@ -77,7 +77,7 @@ trait WebhooksBase
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'attributeId' => 'extra', 'key' => 'extra',
'size' => 64, 'size' => 64,
'required' => false, 'required' => false,
]); ]);

View file

@ -64,7 +64,7 @@ class WebhooksCustomServerTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), [ ]), [
'indexId' => 'fullname', 'key' => 'fullname',
'type' => 'key', 'type' => 'key',
'attributes' => ['lastName', 'firstName'], 'attributes' => ['lastName', 'firstName'],
'orders' => ['ASC', 'ASC'], 'orders' => ['ASC', 'ASC'],
@ -125,7 +125,7 @@ class WebhooksCustomServerTest extends Scope
'write' => ['role:all'], 'write' => ['role:all'],
'permission' => 'document' 'permission' => 'document'
]); ]);
$this->assertEquals($actors['headers']['status-code'], 201); $this->assertEquals($actors['headers']['status-code'], 201);
$this->assertNotEmpty($actors['body']['$id']); $this->assertNotEmpty($actors['body']['$id']);
@ -134,7 +134,7 @@ class WebhooksCustomServerTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'] 'x-appwrite-key' => $this->getProject()['apiKey']
]), []); ]), []);
$this->assertEquals($actors['headers']['status-code'], 204); $this->assertEquals($actors['headers']['status-code'], 204);
$webhook = $this->getLastRequest(); $webhook = $this->getLastRequest();

View file

@ -2,7 +2,7 @@
namespace Appwrite\Tests; namespace Appwrite\Tests;
use Appwrite\Database\Document; use Utopia\Database\Document;
use Appwrite\Messaging\Adapter\Realtime; use Appwrite\Messaging\Adapter\Realtime;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
@ -195,4 +195,51 @@ class MessagingTest extends TestCase
$this->assertArrayHasKey('account', $channels); $this->assertArrayHasKey('account', $channels);
$this->assertArrayNotHasKey('account.456', $channels); $this->assertArrayNotHasKey('account.456', $channels);
} }
public function testFromPayloadCollectionLevelPermissions(): void
{
/**
* Test Collection Level Permissions
*/
$result = Realtime::fromPayload(
event: 'database.documents.create',
payload: new Document([
'$id' => 'test',
'$collection' => 'collection',
'$read' => ['role:admin'],
'$write' => ['role:admin']
]),
collection: new Document([
'$id' => 'collection',
'$read' => ['role:all'],
'$write' => ['role:all'],
'permission' => 'collection'
])
);
$this->assertContains('role:all', $result['roles']);
$this->assertNotContains('role:admin', $result['roles']);
/**
* Test Document Level Permissions
*/
$result = Realtime::fromPayload(
event: 'database.documents.create',
payload: new Document([
'$id' => 'test',
'$collection' => 'collection',
'$read' => ['role:all'],
'$write' => ['role:all']
]),
collection: new Document([
'$id' => 'collection',
'$read' => ['role:admin'],
'$write' => ['role:admin'],
'permission' => 'document'
])
);
$this->assertContains('role:all', $result['roles']);
$this->assertNotContains('role:admin', $result['roles']);
}
} }