diff --git a/app/config/collections2.php b/app/config/collections2.php index 9ba9635457..4f407f3ee4 100644 --- a/app/config/collections2.php +++ b/app/config/collections2.php @@ -174,7 +174,7 @@ $collections = [ 'required' => false, 'default' => null, 'array' => false, - 'filters' => [], + 'filters' => ['casting'], ], [ '$id' => 'signed', @@ -214,7 +214,7 @@ $collections = [ 'required' => false, 'default' => new stdClass, 'array' => false, - 'filters' => ['json'], + 'filters' => ['json', 'range'], ], [ '$id' => 'filters', diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 26d93ae5be..0b288a9ab8 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -25,32 +25,39 @@ use Utopia\Database\Exception\Authorization as AuthorizationException; use Utopia\Database\Exception\Duplicate as DuplicateException; use Utopia\Database\Exception\Limit as LimitException; use Utopia\Database\Exception\Structure as StructureException; -use Appwrite\Utopia\Response; use Appwrite\Database\Validator\CustomId; +use Appwrite\Network\Validator\Email; +use Appwrite\Network\Validator\IP; +use Appwrite\Network\Validator\URL; +use Appwrite\Utopia\Response; use DeviceDetector\DeviceDetector; use Utopia\Database\Validator\Authorization; -$attributesCallback = function ($collectionId, $attribute, $response, $dbForInternal, $database, $audits, $usage) { - /** @var Utopia\Database\Document $attribute*/ - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForInternal*/ - /** @var Appwrite\Event\Event $database */ - /** @var Appwrite\Event\Event $audits */ - /** @var Appwrite\Stats\Stats $usage */ - +/** + * Create attribute of varying type + * + * @param string $collectionId + * @param Utopia\Database\Document $attribute + * @param Appwrite\Utopia\Response $response + * @param Utopia\Database\Database $dbForInternal + * @param Appwrite\Event\Event $database + * @param Appwrite\Event\Event $audits + * @param Appwrite\Stats\Stats $usage + * + * @return Document Newly created attribute document + */ +function createAttribute($collectionId, $attribute, $response, $dbForInternal, $database, $audits, $usage): Document +{ $attributeId = $attribute->getId(); $type = $attribute->getAttribute('type', ''); $size = $attribute->getAttribute('size', 0); $required = $attribute->getAttribute('required', true); - $min = $attribute->getAttribute('min', null); - $max = $attribute->getAttribute('max', null); $signed = $attribute->getAttribute('signed', true); // integers are signed by default $array = $attribute->getAttribute('array', false); $format = $attribute->getAttribute('format', ''); $formatOptions = $attribute->getAttribute('formatOptions', []); $filters = $attribute->getAttribute('filters', []); // filters are hidden from the endpoint $default = $attribute->getAttribute('default', null); - $default = (empty($default)) ? null : (int)$default; $collection = $dbForInternal->getDocument('collections', $collectionId); @@ -58,21 +65,17 @@ $attributesCallback = function ($collectionId, $attribute, $response, $dbForInte throw new Exception('Collection not found', 404); } - // TODO@kodumbeats how to depend on $size for Text validator length - // Ensure attribute default is within required size - if ($size > 0 && !\is_null($default)) { - $validator = new Text($size); - if (!$validator->isValid($default)) { - throw new Exception('Length of default attribute exceeds attribute size', 400); - } - } - if (!empty($format)) { if (!Structure::hasFormat($format, $type)) { throw new Exception("Format {$format} not available for {$type} attributes.", 400); } } + // Must throw here since dbForExternal->createAttribute is performed by db worker + if ($required && $default) { + throw new Exception('Cannot set default value for required attribute', 400); + } + try { $attribute = $dbForInternal->createDocument('attributes', new Document([ '$id' => $collectionId.'_'.$attributeId, @@ -93,12 +96,16 @@ $attributesCallback = function ($collectionId, $attribute, $response, $dbForInte throw new Exception('Attribute already exists', 409); } - $dbForInternal->purgeDocument('collections', $collectionId); + $dbForInternal->deleteCachedDocument('collections', $collectionId); + + // Pass clone of $attribute object to workers + // so we can later modify Document to fit response model + $clone = clone $attribute; $database ->setParam('type', DATABASE_TYPE_CREATE_ATTRIBUTE) ->setParam('collection', $collection) - ->setParam('document', $attribute) + ->setParam('document', $clone) ; $usage->setParam('database.collections.update', 1); @@ -106,11 +113,12 @@ $attributesCallback = function ($collectionId, $attribute, $response, $dbForInte $audits ->setParam('event', 'database.attributes.create') ->setParam('resource', 'collection/'.$collection->getId()) - ->setParam('data', $attribute) + ->setParam('data', $clone) ; $response->setStatusCode(Response::STATUS_CODE_CREATED); - $response->dynamic($attribute, Response::MODEL_ATTRIBUTE); + + return $attribute; }; App::post('/v1/database/collections') @@ -655,7 +663,7 @@ App::post('/v1/database/collections/:collectionId/attributes/string') ->label('sdk.description', '/docs/references/database/create-attribute-string.md') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_ATTRIBUTE) + ->label('sdk.response.model', Response::MODEL_ATTRIBUTE_STRING) ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection using the Database service [server integration](/docs/server/database#createCollection).') ->param('attributeId', '', new Key(), 'Attribute ID.') ->param('size', null, new Integer(), 'Attribute size for text attributes, in number of characters.') @@ -667,14 +675,20 @@ App::post('/v1/database/collections/:collectionId/attributes/string') ->inject('database') ->inject('audits') ->inject('usage') - ->action(function ($collectionId, $attributeId, $size, $required, $default, $array, $response, $dbForInternal, $database, $audits, $usage) use ($attributesCallback) { + ->action(function ($collectionId, $attributeId, $size, $required, $default, $array, $response, $dbForInternal, $database, $audits, $usage) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal*/ /** @var Appwrite\Event\Event $database */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Stats\Stats $usage */ - return $attributesCallback($collectionId, new Document([ + // Ensure attribute default is within required size + $validator = new Text($size); + if (!is_null($default) && !$validator->isValid($default)) { + throw new Exception($validator->getDescription(), 400); + } + + $attribute = createAttribute($collectionId, new Document([ '$id' => $attributeId, 'type' => Database::VAR_STRING, 'size' => $size, @@ -682,6 +696,8 @@ App::post('/v1/database/collections/:collectionId/attributes/string') 'default' => $default, 'array' => $array, ]), $response, $dbForInternal, $database, $audits, $usage); + + $response->dynamic($attribute, Response::MODEL_ATTRIBUTE_STRING); }); App::post('/v1/database/collections/:collectionId/attributes/email') @@ -695,33 +711,35 @@ App::post('/v1/database/collections/:collectionId/attributes/email') ->label('sdk.description', '/docs/references/database/create-attribute-email.md') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_ATTRIBUTE) + ->label('sdk.response.model', Response::MODEL_ATTRIBUTE_EMAIL) ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection using the Database service [server integration](/docs/server/database#createCollection).') ->param('attributeId', '', new Key(), 'Attribute ID.') ->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 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) ->inject('response') ->inject('dbForInternal') ->inject('database') ->inject('audits') ->inject('usage') - ->action(function ($collectionId, $attributeId, $required, $default, $array, $response, $dbForInternal, $database, $audits, $usage) use ($attributesCallback) { + ->action(function ($collectionId, $attributeId, $required, $default, $array, $response, $dbForInternal, $database, $audits, $usage) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal*/ /** @var Appwrite\Event\Event $database */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Stats\Stats $usage */ - return $attributesCallback($collectionId, new Document([ + $attribute = createAttribute($collectionId, new Document([ '$id' => $attributeId, 'type' => Database::VAR_STRING, 'size' => 254, 'required' => $required, 'default' => $default, 'array' => $array, - 'format' => 'email', + 'format' => APP_DATABASE_ATTRIBUTE_EMAIL, ]), $response, $dbForInternal, $database, $audits, $usage); + + $response->dynamic($attribute, Response::MODEL_ATTRIBUTE_EMAIL); }); App::post('/v1/database/collections/:collectionId/attributes/ip') @@ -735,37 +753,39 @@ App::post('/v1/database/collections/:collectionId/attributes/ip') ->label('sdk.description', '/docs/references/database/create-attribute-ip.md') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_ATTRIBUTE) + ->label('sdk.response.model', Response::MODEL_ATTRIBUTE_IP) ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection using the Database service [server integration](/docs/server/database#createCollection).') ->param('attributeId', '', new Key(), 'Attribute ID.') ->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 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) ->inject('response') ->inject('dbForInternal') ->inject('database') ->inject('audits') ->inject('usage') - ->action(function ($collectionId, $attributeId, $required, $default, $array, $response, $dbForInternal, $database, $audits, $usage) use ($attributesCallback) { + ->action(function ($collectionId, $attributeId, $required, $default, $array, $response, $dbForInternal, $database, $audits, $usage) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal*/ /** @var Appwrite\Event\Event $database */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Stats\Stats $usage */ - return $attributesCallback($collectionId, new Document([ + $attribute = createAttribute($collectionId, new Document([ '$id' => $attributeId, 'type' => Database::VAR_STRING, 'size' => 39, 'required' => $required, 'default' => $default, 'array' => $array, - 'format' => 'ip', + 'format' => APP_DATABASE_ATTRIBUTE_IP, ]), $response, $dbForInternal, $database, $audits, $usage); + + $response->dynamic($attribute, Response::MODEL_ATTRIBUTE_IP); }); App::post('/v1/database/collections/:collectionId/attributes/url') - ->desc('Create IP Address Attribute') + ->desc('Create URL Attribute') ->groups(['api', 'database']) ->label('event', 'database.attributes.create') ->label('scope', 'collections.write') @@ -775,33 +795,35 @@ App::post('/v1/database/collections/:collectionId/attributes/url') ->label('sdk.description', '/docs/references/database/create-attribute-url.md') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_ATTRIBUTE) + ->label('sdk.response.model', Response::MODEL_ATTRIBUTE_URL) ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection using the Database service [server integration](/docs/server/database#createCollection).') ->param('attributeId', '', new Key(), 'Attribute ID.') ->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 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) ->inject('response') ->inject('dbForInternal') ->inject('database') ->inject('audits') ->inject('usage') - ->action(function ($collectionId, $attributeId, $required, $default, $array, $response, $dbForInternal, $database, $audits, $usage) use ($attributesCallback) { + ->action(function ($collectionId, $attributeId, $required, $default, $array, $response, $dbForInternal, $database, $audits, $usage) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForExternal*/ /** @var Appwrite\Event\Event $database */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Stats\Stats $usage */ - return $attributesCallback($collectionId, new Document([ + $attribute = createAttribute($collectionId, new Document([ '$id' => $attributeId, 'type' => Database::VAR_STRING, 'size' => 2000, 'required' => $required, 'default' => $default, 'array' => $array, - 'format' => 'url', + 'format' => APP_DATABASE_ATTRIBUTE_URL, ]), $response, $dbForInternal, $database, $audits, $usage); + + $response->dynamic($attribute, Response::MODEL_ATTRIBUTE_URL); }); App::post('/v1/database/collections/:collectionId/attributes/integer') @@ -815,7 +837,7 @@ App::post('/v1/database/collections/:collectionId/attributes/integer') ->label('sdk.description', '/docs/references/database/create-attribute-integer.md') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_ATTRIBUTE) + ->label('sdk.response.model', Response::MODEL_ATTRIBUTE_INTEGER) ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection using the Database service [server integration](/docs/server/database#createCollection).') ->param('attributeId', '', new Key(), 'Attribute ID.') ->param('required', null, new Boolean(), 'Is attribute required?') @@ -828,26 +850,44 @@ App::post('/v1/database/collections/:collectionId/attributes/integer') ->inject('database') ->inject('audits') ->inject('usage') - ->action(function ($collectionId, $attributeId, $required, $min, $max, $default, $array, $response, $dbForInternal, $database, $audits, $usage) use ($attributesCallback) { + ->action(function ($collectionId, $attributeId, $required, $min, $max, $default, $array, $response, $dbForInternal, $database, $audits, $usage) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal*/ /** @var Appwrite\Event\Event $database */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Stats\Stats $usage */ - return $attributesCallback($collectionId, new Document([ + // Ensure attribute default is within range + $min = (is_null($min)) ? PHP_INT_MIN : \intval($min); + $max = (is_null($max)) ? PHP_INT_MAX : \intval($max); + $validator = new Range($min, $max, Database::VAR_INTEGER); + + if (!is_null($default) && !$validator->isValid($default)) { + throw new Exception($validator->getDescription(), 400); + } + + $attribute = createAttribute($collectionId, new Document([ '$id' => $attributeId, 'type' => Database::VAR_INTEGER, 'size' => 0, 'required' => $required, 'default' => $default, 'array' => $array, - 'format' => 'int-range', + 'format' => APP_DATABASE_ATTRIBUTE_INT_RANGE, 'formatOptions' => [ - 'min' => (is_null($min)) ? PHP_INT_MIN : \intval($min), - 'max' => (is_null($max)) ? PHP_INT_MAX : \intval($max), + 'min' => $min, + 'max' => $max, ], ]), $response, $dbForInternal, $database, $audits, $usage); + + $formatOptions = $attribute->getAttribute('formatOptions', []); + + if (!empty($formatOptions)) { + $attribute->setAttribute('min', \intval($formatOptions['min'])); + $attribute->setAttribute('max', \intval($formatOptions['max'])); + } + + $response->dynamic($attribute, Response::MODEL_ATTRIBUTE_INTEGER); }); App::post('/v1/database/collections/:collectionId/attributes/float') @@ -861,7 +901,7 @@ App::post('/v1/database/collections/:collectionId/attributes/float') ->label('sdk.description', '/docs/references/database/create-attribute-float.md') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_ATTRIBUTE) + ->label('sdk.response.model', Response::MODEL_ATTRIBUTE_FLOAT) ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection using the Database service [server integration](/docs/server/database#createCollection).') ->param('attributeId', '', new Key(), 'Attribute ID.') ->param('required', null, new Boolean(), 'Is attribute required?') @@ -874,26 +914,44 @@ App::post('/v1/database/collections/:collectionId/attributes/float') ->inject('database') ->inject('audits') ->inject('usage') - ->action(function ($collectionId, $attributeId, $required, $min, $max, $default, $array, $response, $dbForInternal, $database, $audits, $usage) use ($attributesCallback) { + ->action(function ($collectionId, $attributeId, $required, $min, $max, $default, $array, $response, $dbForInternal, $database, $audits, $usage) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal*/ /** @var Appwrite\Event\Event $database */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Stats\Stats $usage */ - return $attributesCallback($collectionId, new Document([ + // Ensure attribute default is within range + $min = (is_null($min)) ? PHP_FLOAT_MIN : \floatval($min); + $max = (is_null($max)) ? PHP_FLOAT_MAX : \floatval($max); + $validator = new Range($min, $max, Database::VAR_FLOAT); + + if (!is_null($default) && !$validator->isValid($default)) { + throw new Exception($validator->getDescription(), 400); + } + + $attribute = createAttribute($collectionId, new Document([ '$id' => $attributeId, 'type' => Database::VAR_FLOAT, 'required' => $required, 'size' => 0, 'default' => $default, 'array' => $array, - 'format' => 'float-range', + 'format' => APP_DATABASE_ATTRIBUTE_FLOAT_RANGE, 'formatOptions' => [ - 'min' => (is_null($min)) ? PHP_FLOAT_MIN : \floatval($min), - 'max' => (is_null($max)) ? PHP_FLOAT_MAX : \floatval($max), + 'min' => $min, + 'max' => $max, ], ]), $response, $dbForInternal, $database, $audits, $usage); + + $formatOptions = $attribute->getAttribute('formatOptions', []); + + if (!empty($formatOptions)) { + $attribute->setAttribute('min', \floatval($formatOptions['min'])); + $attribute->setAttribute('max', \floatval($formatOptions['max'])); + } + + $response->dynamic($attribute, Response::MODEL_ATTRIBUTE_FLOAT); }); App::post('/v1/database/collections/:collectionId/attributes/boolean') @@ -907,7 +965,7 @@ App::post('/v1/database/collections/:collectionId/attributes/boolean') ->label('sdk.description', '/docs/references/database/create-attribute-boolean.md') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_ATTRIBUTE) + ->label('sdk.response.model', Response::MODEL_ATTRIBUTE_BOOLEAN) ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection using the Database service [server integration](/docs/server/database#createCollection).') ->param('attributeId', '', new Key(), 'Attribute ID.') ->param('required', null, new Boolean(), 'Is attribute required?') @@ -918,14 +976,14 @@ App::post('/v1/database/collections/:collectionId/attributes/boolean') ->inject('database') ->inject('audits') ->inject('usage') - ->action(function ($collectionId, $attributeId, $required, $default, $array, $response, $dbForInternal, $database, $audits, $usage) use ($attributesCallback) { + ->action(function ($collectionId, $attributeId, $required, $default, $array, $response, $dbForInternal, $database, $audits, $usage) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal*/ /** @var Appwrite\Event\Event $database */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Stats\Stats $usage */ - return $attributesCallback($collectionId, new Document([ + $attribute = createAttribute($collectionId, new Document([ '$id' => $attributeId, 'type' => Database::VAR_BOOLEAN, 'size' => 0, @@ -933,6 +991,8 @@ App::post('/v1/database/collections/:collectionId/attributes/boolean') 'default' => $default, 'array' => $array, ]), $response, $dbForInternal, $database, $audits, $usage); + + $response->dynamic($attribute, Response::MODEL_ATTRIBUTE_BOOLEAN); }); App::get('/v1/database/collections/:collectionId/attributes') @@ -960,13 +1020,7 @@ App::get('/v1/database/collections/:collectionId/attributes') throw new Exception('Collection not found', 404); } - $attributes = $collection->getAttributes(); - - $attributes = array_map(function ($attribute) use ($collection) { - return new Document([\array_merge($attribute, [ - 'collectionId' => $collection->getId(), - ])]); - }, $attributes); + $attributes = $collection->getAttribute('attributes'); $usage->setParam('database.collections.read', 1); @@ -986,7 +1040,14 @@ App::get('/v1/database/collections/:collectionId/attributes/:attributeId') ->label('sdk.description', '/docs/references/database/get-attribute.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_ATTRIBUTE) + ->label('sdk.response.model', [ + Response::MODEL_ATTRIBUTE_BOOLEAN, + Response::MODEL_ATTRIBUTE_INTEGER, + Response::MODEL_ATTRIBUTE_FLOAT, + Response::MODEL_ATTRIBUTE_EMAIL, + Response::MODEL_ATTRIBUTE_URL, + Response::MODEL_ATTRIBUTE_IP, + Response::MODEL_ATTRIBUTE_STRING,])// needs to be last, since its condition would dominate any other string attribute ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection using the Database service [server integration](/docs/server/database#createCollection).') ->param('attributeId', '', new Key(), 'Attribute ID.') ->inject('response') @@ -1002,22 +1063,32 @@ App::get('/v1/database/collections/:collectionId/attributes/:attributeId') throw new Exception('Collection not found', 404); } - $attributes = $collection->getAttributes(); + $attribute = $collection->find('$id', $attributeId, 'attributes'); - // Search for attribute - $attributeIndex = array_search($attributeId, array_column($attributes, '$id')); - - if ($attributeIndex === false) { + if (!$attribute) { throw new Exception('Attribute not found', 404); } - $attribute = new Document([\array_merge($attributes[$attributeIndex], [ - 'collectionId' => $collectionId, - ])]); + // Select response model based on type and format + $type = $attribute->getAttribute('type'); + $format = $attribute->getAttribute('format'); + + $model = match($type) { + Database::VAR_BOOLEAN => Response::MODEL_ATTRIBUTE_BOOLEAN, + Database::VAR_INTEGER => Response::MODEL_ATTRIBUTE_INTEGER, + Database::VAR_FLOAT => Response::MODEL_ATTRIBUTE_FLOAT, + Database::VAR_STRING => match($format) { + APP_DATABASE_ATTRIBUTE_EMAIL => Response::MODEL_ATTRIBUTE_EMAIL, + APP_DATABASE_ATTRIBUTE_IP => Response::MODEL_ATTRIBUTE_IP, + APP_DATABASE_ATTRIBUTE_URL => Response::MODEL_ATTRIBUTE_URL, + default => Response::MODEL_ATTRIBUTE_STRING, + }, + default => Response::MODEL_ATTRIBUTE, + }; $usage->setParam('database.collections.read', 1); - - $response->dynamic($attribute, Response::MODEL_ATTRIBUTE); + + $response->dynamic($attribute, $model); }); App::delete('/v1/database/collections/:collectionId/attributes/:attributeId') @@ -1060,7 +1131,7 @@ App::delete('/v1/database/collections/:collectionId/attributes/:attributeId') } $attribute = $dbForInternal->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'deleting')); - $dbForInternal->purgeDocument('collections', $collectionId); + $dbForInternal->deleteCachedDocument('collections', $collectionId); $database ->setParam('type', DATABASE_TYPE_DELETE_ATTRIBUTE) @@ -1167,7 +1238,7 @@ App::post('/v1/database/collections/:collectionId/indexes') throw new Exception('Index already exists', 409); } - $dbForInternal->purgeDocument('collections', $collectionId); + $dbForInternal->deleteCachedDocument('collections', $collectionId); $database ->setParam('type', DATABASE_TYPE_CREATE_INDEX) @@ -1312,7 +1383,7 @@ App::delete('/v1/database/collections/:collectionId/indexes/:indexId') } $index = $dbForInternal->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'deleting')); - $dbForInternal->purgeDocument('collections', $collectionId); + $dbForInternal->deleteCachedDocument('collections', $collectionId); $database ->setParam('type', DATABASE_TYPE_DELETE_INDEX) diff --git a/app/controllers/web/home.php b/app/controllers/web/home.php index 1d9e86d873..9f4d2412a8 100644 --- a/app/controllers/web/home.php +++ b/app/controllers/web/home.php @@ -387,11 +387,10 @@ App::get('/specs/:format') } $routes[] = $route; - $model = $response->getModel($route->getLabel('sdk.response.model', 'none')); - - if($model) { - $models[$model->getType()] = $model; - } + $modelLabel = $route->getLabel('sdk.response.model', 'none'); + $model = \is_array($modelLabel) ? \array_map(function($m) use($response) { + return $response->getModel($m); + }, $modelLabel) : $response->getModel($modelLabel); } } diff --git a/app/init.php b/app/init.php index 4c1e6cf219..2d0bb906c3 100644 --- a/app/init.php +++ b/app/init.php @@ -63,6 +63,11 @@ const APP_LIMIT_COUNT = 5000; const APP_LIMIT_USERS = 10000; const APP_CACHE_BUSTER = 151; const APP_VERSION_STABLE = '0.9.4'; +const APP_DATABASE_ATTRIBUTE_EMAIL = 'email'; +const APP_DATABASE_ATTRIBUTE_IP = 'ip'; +const APP_DATABASE_ATTRIBUTE_URL = 'url'; +const APP_DATABASE_ATTRIBUTE_INT_RANGE = 'intRange'; +const APP_DATABASE_ATTRIBUTE_FLOAT_RANGE = 'floatRange'; const APP_STORAGE_UPLOADS = '/storage/uploads'; const APP_STORAGE_FUNCTIONS = '/storage/functions'; const APP_STORAGE_CACHE = '/storage/cache'; @@ -142,7 +147,7 @@ if(!empty($user) || !empty($pass)) { } /** - * DB Filters + * Old DB Filters */ DatabaseOld::addFilter('json', function($value) { @@ -178,6 +183,43 @@ DatabaseOld::addFilter('encrypt', } ); +/** + * New DB Filters + */ +Database::addFilter('casting', + function($value) { + return json_encode(['value' => $value]); + }, + function($value) { + if (is_null($value)) { + return null; + } + return json_decode($value, true)['value']; + } +); + +Database::addFilter('range', + function($value, Document $attribute) { + if ($attribute->isSet('min')) { + $attribute->removeAttribute('min'); + } + if ($attribute->isSet('max')) { + $attribute->removeAttribute('max'); + } + return $value; + }, + function($value, Document $attribute) { + $formatOptions = json_decode($attribute->getAttribute('formatOptions', []), true); + if (isset($formatOptions['min']) || isset($formatOptions['max'])) { + $attribute + ->setAttribute('min', $formatOptions['min']) + ->setAttribute('max', $formatOptions['max']) + ; + } + return $value; + } +); + Database::addFilter('subQueryAttributes', function($value) { return null; @@ -226,25 +268,25 @@ Database::addFilter('encrypt', /** * DB Formats */ -Structure::addFormat('email', function() { +Structure::addFormat(APP_DATABASE_ATTRIBUTE_EMAIL, function() { return new Email(); }, Database::VAR_STRING); -Structure::addFormat('ip', function() { +Structure::addFormat(APP_DATABASE_ATTRIBUTE_IP, function() { return new IP(); }, Database::VAR_STRING); -Structure::addFormat('url', function() { +Structure::addFormat(APP_DATABASE_ATTRIBUTE_URL, function() { return new URL(); }, Database::VAR_STRING); -Structure::addFormat('int-range', function($attribute) { +Structure::addFormat(APP_DATABASE_ATTRIBUTE_INT_RANGE, function($attribute) { $min = $attribute['formatOptions']['min'] ?? -INF; $max = $attribute['formatOptions']['max'] ?? INF; return new Range($min, $max, Range::TYPE_INTEGER); }, Database::VAR_INTEGER); -Structure::addFormat('float-range', function($attribute) { +Structure::addFormat(APP_DATABASE_ATTRIBUTE_FLOAT_RANGE, function($attribute) { $min = $attribute['formatOptions']['min'] ?? -INF; $max = $attribute['formatOptions']['max'] ?? INF; return new Range($min, $max, Range::TYPE_FLOAT); diff --git a/app/workers/database.php b/app/workers/database.php index 0ca7aa45f3..8ffe3ffc38 100644 --- a/app/workers/database.php +++ b/app/workers/database.php @@ -94,7 +94,7 @@ class DatabaseV1 extends Worker $dbForInternal->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'failed')); } - $dbForInternal->purgeDocument('collections', $collectionId); + $dbForInternal->deleteCachedDocument('collections', $collectionId); } /** @@ -171,7 +171,7 @@ class DatabaseV1 extends Worker } } - $dbForInternal->purgeDocument('collections', $collectionId); + $dbForInternal->deleteCachedDocument('collections', $collectionId); } /** @@ -201,7 +201,7 @@ class DatabaseV1 extends Worker $dbForInternal->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'failed')); } - $dbForInternal->purgeDocument('collections', $collectionId); + $dbForInternal->deleteCachedDocument('collections', $collectionId); } /** @@ -228,6 +228,6 @@ class DatabaseV1 extends Worker $dbForInternal->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'failed')); } - $dbForInternal->purgeDocument('collections', $collectionId); + $dbForInternal->deleteCachedDocument('collections', $collectionId); } } diff --git a/composer.json b/composer.json index 3635007043..b3d21a4b02 100644 --- a/composer.json +++ b/composer.json @@ -45,7 +45,7 @@ "utopia-php/cache": "0.4.*", "utopia-php/cli": "0.11.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "dev-feat-adjusted-query-validator as 0.10.0", + "utopia-php/database": "0.10.0", "utopia-php/locale": "0.4.*", "utopia-php/registry": "0.5.*", "utopia-php/preloader": "0.2.*", @@ -63,12 +63,7 @@ "adhocore/jwt": "1.1.2", "slickdeals/statsd": "3.1.0" }, - "repositories": [ - { - "type": "git", - "url": "https://github.com/utopia-php/database" - } - ], + "repositories": [], "require-dev": { "appwrite/sdk-generator": "0.13.0", "swoole/ide-helper": "4.6.7", @@ -83,4 +78,4 @@ "php": "8.0" } } -} \ No newline at end of file +} diff --git a/composer.lock b/composer.lock index 8dac7bcc6d..7eee04c864 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "175f077512c575216c4c88f1d33c6d00", + "content-hash": "dfb8fa19daa736b3687617c98f309983", "packages": [ { "name": "adhocore/jwt", @@ -248,16 +248,16 @@ }, { "name": "chillerlan/php-settings-container", - "version": "2.1.1", + "version": "2.1.2", "source": { "type": "git", "url": "https://github.com/chillerlan/php-settings-container.git", - "reference": "98ccc1b31b31a53bcb563465c4961879b2b93096" + "reference": "ec834493a88682dd69652a1eeaf462789ed0c5f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/98ccc1b31b31a53bcb563465c4961879b2b93096", - "reference": "98ccc1b31b31a53bcb563465c4961879b2b93096", + "url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/ec834493a88682dd69652a1eeaf462789ed0c5f5", + "reference": "ec834493a88682dd69652a1eeaf462789ed0c5f5", "shasum": "" }, "require": { @@ -307,7 +307,7 @@ "type": "ko_fi" } ], - "time": "2021-01-06T15:57:03+00:00" + "time": "2021-09-06T15:17:01+00:00" }, { "name": "colinmollenhour/credis", @@ -355,16 +355,16 @@ }, { "name": "composer/package-versions-deprecated", - "version": "1.11.99.3", + "version": "1.11.99.4", "source": { "type": "git", "url": "https://github.com/composer/package-versions-deprecated.git", - "reference": "fff576ac850c045158a250e7e27666e146e78d18" + "reference": "b174585d1fe49ceed21928a945138948cb394600" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/fff576ac850c045158a250e7e27666e146e78d18", - "reference": "fff576ac850c045158a250e7e27666e146e78d18", + "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b174585d1fe49ceed21928a945138948cb394600", + "reference": "b174585d1fe49ceed21928a945138948cb394600", "shasum": "" }, "require": { @@ -408,7 +408,7 @@ "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", "support": { "issues": "https://github.com/composer/package-versions-deprecated/issues", - "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.3" + "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.4" }, "funding": [ { @@ -424,7 +424,7 @@ "type": "tidelift" } ], - "time": "2021-08-17T13:49:14+00:00" + "time": "2021-09-13T08:41:34+00:00" }, { "name": "dragonmantank/cron-expression", @@ -1984,11 +1984,17 @@ }, { "name": "utopia-php/database", - "version": "dev-feat-adjusted-query-validator", + "version": "0.10.0", "source": { "type": "git", - "url": "https://github.com/utopia-php/database", - "reference": "cb73391371f70ddb54bc0000064b15c5f173cb7a" + "url": "https://github.com/utopia-php/database.git", + "reference": "b7c60b0ec769a9050dd2b939b78ff1f5d4fa27e8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/database/zipball/b7c60b0ec769a9050dd2b939b78ff1f5d4fa27e8", + "reference": "b7c60b0ec769a9050dd2b939b78ff1f5d4fa27e8", + "shasum": "" }, "require": { "ext-mongodb": "*", @@ -2011,11 +2017,7 @@ "Utopia\\Database\\": "src/Database" } }, - "autoload-dev": { - "psr-4": { - "Utopia\\Tests\\": "tests/Database" - } - }, + "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -2037,7 +2039,11 @@ "upf", "utopia" ], - "time": "2021-08-23T14:18:47+00:00" + "support": { + "issues": "https://github.com/utopia-php/database/issues", + "source": "https://github.com/utopia-php/database/tree/0.10.0" + }, + "time": "2021-10-04T17:23:25+00:00" }, { "name": "utopia-php/domains", @@ -2576,16 +2582,16 @@ "packages-dev": [ { "name": "amphp/amp", - "version": "v2.6.0", + "version": "v2.6.1", "source": { "type": "git", "url": "https://github.com/amphp/amp.git", - "reference": "caa95edeb1ca1bf7532e9118ede4a3c3126408cc" + "reference": "c5fc66a78ee38d7ac9195a37bacaf940eb3f65ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/amp/zipball/caa95edeb1ca1bf7532e9118ede4a3c3126408cc", - "reference": "caa95edeb1ca1bf7532e9118ede4a3c3126408cc", + "url": "https://api.github.com/repos/amphp/amp/zipball/c5fc66a78ee38d7ac9195a37bacaf940eb3f65ae", + "reference": "c5fc66a78ee38d7ac9195a37bacaf940eb3f65ae", "shasum": "" }, "require": { @@ -2653,7 +2659,7 @@ "support": { "irc": "irc://irc.freenode.org/amphp", "issues": "https://github.com/amphp/amp/issues", - "source": "https://github.com/amphp/amp/tree/v2.6.0" + "source": "https://github.com/amphp/amp/tree/v2.6.1" }, "funding": [ { @@ -2661,7 +2667,7 @@ "type": "github" } ], - "time": "2021-07-16T20:06:06+00:00" + "time": "2021-09-23T18:43:08+00:00" }, { "name": "amphp/byte-stream", @@ -3383,16 +3389,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.12.0", + "version": "v4.13.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "6608f01670c3cc5079e18c1dab1104e002579143" + "reference": "50953a2691a922aa1769461637869a0a2faa3f53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6608f01670c3cc5079e18c1dab1104e002579143", - "reference": "6608f01670c3cc5079e18c1dab1104e002579143", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/50953a2691a922aa1769461637869a0a2faa3f53", + "reference": "50953a2691a922aa1769461637869a0a2faa3f53", "shasum": "" }, "require": { @@ -3433,9 +3439,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.12.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.0" }, - "time": "2021-07-21T10:44:31+00:00" + "time": "2021-09-20T12:20:58+00:00" }, { "name": "openlss/lib-array2xml", @@ -3712,16 +3718,16 @@ }, { "name": "phpdocumentor/type-resolver", - "version": "1.4.0", + "version": "1.5.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0" + "reference": "a12f7e301eb7258bb68acd89d4aefa05c2906cae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", - "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/a12f7e301eb7258bb68acd89d4aefa05c2906cae", + "reference": "a12f7e301eb7258bb68acd89d4aefa05c2906cae", "shasum": "" }, "require": { @@ -3729,7 +3735,8 @@ "phpdocumentor/reflection-common": "^2.0" }, "require-dev": { - "ext-tokenizer": "*" + "ext-tokenizer": "*", + "psalm/phar": "^4.8" }, "type": "library", "extra": { @@ -3755,39 +3762,39 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.4.0" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.5.1" }, - "time": "2020-09-17T18:55:26+00:00" + "time": "2021-10-02T14:08:47+00:00" }, { "name": "phpspec/prophecy", - "version": "1.13.0", + "version": "1.14.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea" + "reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/be1996ed8adc35c3fd795488a653f4b518be70ea", - "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e", + "reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e", "shasum": "" }, "require": { "doctrine/instantiator": "^1.2", - "php": "^7.2 || ~8.0, <8.1", + "php": "^7.2 || ~8.0, <8.2", "phpdocumentor/reflection-docblock": "^5.2", "sebastian/comparator": "^3.0 || ^4.0", "sebastian/recursion-context": "^3.0 || ^4.0" }, "require-dev": { - "phpspec/phpspec": "^6.0", + "phpspec/phpspec": "^6.0 || ^7.0", "phpunit/phpunit": "^8.0 || ^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11.x-dev" + "dev-master": "1.x-dev" } }, "autoload": { @@ -3822,29 +3829,29 @@ ], "support": { "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/1.13.0" + "source": "https://github.com/phpspec/prophecy/tree/1.14.0" }, - "time": "2021-03-17T13:42:18+00:00" + "time": "2021-09-10T09:02:12+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.6", + "version": "9.2.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "f6293e1b30a2354e8428e004689671b83871edde" + "reference": "d4c798ed8d51506800b441f7a13ecb0f76f12218" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f6293e1b30a2354e8428e004689671b83871edde", - "reference": "f6293e1b30a2354e8428e004689671b83871edde", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/d4c798ed8d51506800b441f7a13ecb0f76f12218", + "reference": "d4c798ed8d51506800b441f7a13ecb0f76f12218", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.10.2", + "nikic/php-parser": "^4.12.0", "php": ">=7.3", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", @@ -3893,7 +3900,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.6" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.7" }, "funding": [ { @@ -3901,7 +3908,7 @@ "type": "github" } ], - "time": "2021-03-28T07:26:59+00:00" + "time": "2021-09-17T05:39:03+00:00" }, { "name": "phpunit/php-file-iterator", @@ -5313,16 +5320,16 @@ }, { "name": "symfony/console", - "version": "v5.3.6", + "version": "v5.3.7", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "51b71afd6d2dc8f5063199357b9880cea8d8bfe2" + "reference": "8b1008344647462ae6ec57559da166c2bfa5e16a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/51b71afd6d2dc8f5063199357b9880cea8d8bfe2", - "reference": "51b71afd6d2dc8f5063199357b9880cea8d8bfe2", + "url": "https://api.github.com/repos/symfony/console/zipball/8b1008344647462ae6ec57559da166c2bfa5e16a", + "reference": "8b1008344647462ae6ec57559da166c2bfa5e16a", "shasum": "" }, "require": { @@ -5392,7 +5399,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.3.6" + "source": "https://github.com/symfony/console/tree/v5.3.7" }, "funding": [ { @@ -5408,7 +5415,7 @@ "type": "tidelift" } ], - "time": "2021-07-27T19:10:22+00:00" + "time": "2021-08-25T20:02:16+00:00" }, { "name": "symfony/deprecation-contracts", @@ -5882,16 +5889,16 @@ }, { "name": "symfony/string", - "version": "v5.3.3", + "version": "v5.3.7", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1" + "reference": "8d224396e28d30f81969f083a58763b8b9ceb0a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1", - "reference": "bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1", + "url": "https://api.github.com/repos/symfony/string/zipball/8d224396e28d30f81969f083a58763b8b9ceb0a5", + "reference": "8d224396e28d30f81969f083a58763b8b9ceb0a5", "shasum": "" }, "require": { @@ -5945,7 +5952,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.3.3" + "source": "https://github.com/symfony/string/tree/v5.3.7" }, "funding": [ { @@ -5961,7 +5968,7 @@ "type": "tidelift" } ], - "time": "2021-06-27T11:44:38+00:00" + "time": "2021-08-26T08:00:08+00:00" }, { "name": "theseer/tokenizer", @@ -6015,16 +6022,16 @@ }, { "name": "twig/twig", - "version": "v2.14.6", + "version": "v2.14.7", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "27e5cf2b05e3744accf39d4c68a3235d9966d260" + "reference": "8e202327ee1ed863629de9b18a5ec70ac614d88f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/27e5cf2b05e3744accf39d4c68a3235d9966d260", - "reference": "27e5cf2b05e3744accf39d4c68a3235d9966d260", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/8e202327ee1ed863629de9b18a5ec70ac614d88f", + "reference": "8e202327ee1ed863629de9b18a5ec70ac614d88f", "shasum": "" }, "require": { @@ -6034,7 +6041,7 @@ }, "require-dev": { "psr/container": "^1.0", - "symfony/phpunit-bridge": "^4.4.9|^5.0.9" + "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" }, "type": "library", "extra": { @@ -6078,7 +6085,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v2.14.6" + "source": "https://github.com/twigphp/Twig/tree/v2.14.7" }, "funding": [ { @@ -6090,7 +6097,7 @@ "type": "tidelift" } ], - "time": "2021-05-16T12:12:47+00:00" + "time": "2021-09-17T08:39:54+00:00" }, { "name": "vimeo/psalm", @@ -6248,18 +6255,9 @@ "time": "2015-12-17T08:42:14+00:00" } ], - "aliases": [ - { - "package": "utopia-php/database", - "version": "dev-feat-adjusted-query-validator", - "alias": "0.10.0", - "alias_normalized": "0.10.0.0" - } - ], + "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "utopia-php/database": 20 - }, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/src/Appwrite/Specification/Format/OpenAPI3.php b/src/Appwrite/Specification/Format/OpenAPI3.php index 5095c5395f..8d375d2f9f 100644 --- a/src/Appwrite/Specification/Format/OpenAPI3.php +++ b/src/Appwrite/Specification/Format/OpenAPI3.php @@ -4,7 +4,6 @@ namespace Appwrite\Specification\Format; use Appwrite\Specification\Format; use Appwrite\Template\Template; -use stdClass; use Utopia\Validator; class OpenAPI3 extends Format @@ -25,21 +24,27 @@ class OpenAPI3 extends Format * Get Used Models * * Recursively get all used models - * + * * @param object $model * @param array $models * * @return void */ protected function getUsedModels($model, array &$usedModels) - { - if (is_string($model) && !in_array($model, ['string', 'integer', 'boolean', 'json', 'float'])) { + { + if (is_string($model) && !in_array($model, ['string', 'integer', 'boolean', 'json', 'float', 'double'])) { $usedModels[] = $model; return; } if (!is_object($model)) return; foreach ($model->getRules() as $rule) { - $this->getUsedModels($rule['type'], $usedModels); + if(\is_array($rule['type'])) { + foreach ($rule['type'] as $type) { + $this->getUsedModels($type, $usedModels); + } + } else { + $this->getUsedModels($rule['type'], $usedModels); + } } } @@ -93,7 +98,7 @@ class OpenAPI3 extends Format if (isset($output['components']['securitySchemes']['Project'])) { $output['components']['securitySchemes']['Project']['x-appwrite'] = ['demo' => '5df5acd0d48c2']; } - + if (isset($output['components']['securitySchemes']['Key'])) { $output['components']['securitySchemes']['Key']['x-appwrite'] = ['demo' => '919c2d18fb5d4...a2ae413da83346ad2']; } @@ -101,7 +106,7 @@ class OpenAPI3 extends Format if (isset($output['securityDefinitions']['JWT'])) { $output['securityDefinitions']['JWT']['x-appwrite'] = ['demo' => 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ...']; } - + if (isset($output['components']['securitySchemes']['Locale'])) { $output['components']['securitySchemes']['Locale']['x-appwrite'] = ['demo' => 'en']; } @@ -125,7 +130,7 @@ class OpenAPI3 extends Format $id = $route->getLabel('sdk.method', \uniqid()); $desc = (!empty($route->getLabel('sdk.description', ''))) ? \realpath(__DIR__.'/../../../../'.$route->getLabel('sdk.description', '')) : null; $produces = $route->getLabel('sdk.response.type', null); - $model = $route->getLabel('sdk.response.model', 'none'); + $model = $route->getLabel('sdk.response.model', 'none'); $routeSecurity = $route->getLabel('sdk.auth', []); $sdkPlatofrms = []; @@ -149,7 +154,7 @@ class OpenAPI3 extends Format if(empty($routeSecurity)) { $sdkPlatofrms[] = APP_PLATFORM_CLIENT; } - + $temp = [ 'summary' => $route->getDesc(), 'operationId' => $route->getLabel('sdk.namespace', 'default').ucfirst($id), @@ -175,13 +180,24 @@ class OpenAPI3 extends Format ]; foreach ($this->models as $key => $value) { - if($value->getType() === $model) { - $model = $value; - break; + if(\is_array($model)) { + $model = \array_map(function($m) use($value) { + if($m === $value->getType()) { + return $value; + } + + return $m; + }, $model); + } else { + if($value->getType() === $model) { + $model = $value; + break; + } } + } - if($model->isNone()) { + if(!(\is_array($model)) && $model->isNone()) { $temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [ 'description' => (in_array($produces, [ 'image/*', @@ -198,17 +214,43 @@ class OpenAPI3 extends Format // ], ]; } else { - $usedModels[] = $model->getType(); - $temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [ - 'description' => $model->getName(), - 'content' => [ - $produces => [ - 'schema' => [ - '$ref' => '#/components/schemas/'.$model->getType(), + if(\is_array($model)) { + $modelDescription = \join(', or ', \array_map(function ($m) { + return $m->getName(); + }, $model)); + + // model has multiple possible responses, we will use oneOf + foreach ($model as $m) { + $usedModels[] = $m->getType(); + } + + $temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [ + 'description' => $modelDescription, + 'content' => [ + $produces => [ + 'schema' => [ + 'oneOf' => \array_map(function($m) { + return ['$ref' => '#/components/schemas/'.$m->getType()]; + }, $model) + ], ], ], - ], - ]; + ]; + } else { + // Response definition using one type + $usedModels[] = $model->getType(); + $temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [ + 'description' => $model->getName(), + 'content' => [ + $produces => [ + 'schema' => [ + '$ref' => '#/components/schemas/'.$model->getType(), + ], + ], + ], + ]; + } + } if($route->getLabel('sdk.response.code', 500) === 204) { @@ -218,7 +260,7 @@ class OpenAPI3 extends Format if ((!empty($scope))) { // && 'public' != $scope $securities = ['Project' => []]; - + foreach($route->getLabel('sdk.auth', []) as $security) { if(array_key_exists($security, $this->keys)) { $securities[$security] = []; @@ -396,7 +438,7 @@ class OpenAPI3 extends Format if($model->isAny()) { $output['components']['schemas'][$model->getType()]['additionalProperties'] = true; } - + if(!empty($required)) { $output['components']['schemas'][$model->getType()]['required'] = $required; } @@ -411,7 +453,7 @@ class OpenAPI3 extends Format case 'json': $type = 'string'; break; - + case 'integer': $type = 'integer'; $format = 'int32'; @@ -421,18 +463,39 @@ class OpenAPI3 extends Format $type = 'number'; $format = 'float'; break; - + + case 'double': + $type = 'number'; + $format = 'double'; + break; + case 'boolean': $type = 'boolean'; break; - + default: $type = 'object'; $rule['type'] = ($rule['type']) ? $rule['type'] : 'none'; - $items = [ - '$ref' => '#/components/schemas/'.$rule['type'], - ]; + if(\is_array($rule['type'])) { + if($rule['array']) { + $items = [ + 'anyOf' => \array_map(function($type) { + return ['$ref' => '#/components/schemas/'.$type]; + }, $rule['type']) + ]; + } else { + $items = [ + 'oneOf' => \array_map(function($type) { + return ['$ref' => '#/components/schemas/'.$type]; + }, $rule['type']) + ]; + } + } else { + $items = [ + '$ref' => '#/components/schemas/'.$rule['type'], + ]; + } break; } diff --git a/src/Appwrite/Specification/Format/Swagger2.php b/src/Appwrite/Specification/Format/Swagger2.php index 2a3be45d17..a8dcffb1e4 100644 --- a/src/Appwrite/Specification/Format/Swagger2.php +++ b/src/Appwrite/Specification/Format/Swagger2.php @@ -33,13 +33,19 @@ class Swagger2 extends Format */ protected function getUsedModels($model, array &$usedModels) { - if (is_string($model) && !in_array($model, ['string', 'integer', 'boolean', 'json', 'float'])) { + if (is_string($model) && !in_array($model, ['string', 'integer', 'boolean', 'json', 'float', 'double'])) { $usedModels[] = $model; return; } if (!is_object($model)) return; foreach ($model->getRules() as $rule) { - $this->getUsedModels($rule['type'], $usedModels); + if(\is_array($rule['type'])) { + foreach ($rule['type'] as $type) { + $this->getUsedModels($type, $usedModels); + } + } else { + $this->getUsedModels($rule['type'], $usedModels); + } } } @@ -91,15 +97,15 @@ class Swagger2 extends Format if (isset($output['securityDefinitions']['Project'])) { $output['securityDefinitions']['Project']['x-appwrite'] = ['demo' => '5df5acd0d48c2']; } - + if (isset($output['securityDefinitions']['Key'])) { $output['securityDefinitions']['Key']['x-appwrite'] = ['demo' => '919c2d18fb5d4...a2ae413da83346ad2']; } - + if (isset($output['securityDefinitions']['JWT'])) { $output['securityDefinitions']['JWT']['x-appwrite'] = ['demo' => 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ...']; } - + if (isset($output['securityDefinitions']['Locale'])) { $output['securityDefinitions']['Locale']['x-appwrite'] = ['demo' => 'en']; } @@ -147,7 +153,7 @@ class Swagger2 extends Format if(empty($routeSecurity)) { $sdkPlatofrms[] = APP_PLATFORM_CLIENT; } - + $temp = [ 'summary' => $route->getDesc(), 'operationId' => $route->getLabel('sdk.namespace', 'default').ucfirst($id), @@ -177,13 +183,22 @@ class Swagger2 extends Format } foreach ($this->models as $key => $value) { - if($value->getType() === $model) { - $model = $value; - break; + if(\is_array($model)) { + $model = \array_map(function($m) use($value) { + if($m === $value->getType()) { + return $value; + } + return $m; + }, $model); + } else { + if($value->getType() === $model) { + $model = $value; + break; + } } } - if($model->isNone()) { + if(!(\is_array($model)) && $model->isNone()) { $temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [ 'description' => (in_array($produces, [ 'image/*', @@ -200,13 +215,41 @@ class Swagger2 extends Format ], ]; } else { - $usedModels[] = $model->getType(); - $temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [ - 'description' => $model->getName(), - 'schema' => [ - '$ref' => '#/definitions/'.$model->getType(), - ], - ]; + + if(\is_array($model)) { + $modelDescription = \join(', or ', \array_map(function ($m) { + return $m->getName(); + }, $model)); + // model has multiple possible responses, we will use oneOf + foreach ($model as $m) { + $usedModels[] = $m->getType(); + } + $temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [ + 'description' => $modelDescription, + 'content' => [ + $produces => [ + 'schema' => [ + 'oneOf' => \array_map(function($m) { + return ['$ref' => '#/definitions/'.$m->getType()]; + }, $model) + ], + ], + ], + ]; + } else { + // Response definition using one type + $usedModels[] = $model->getType(); + $temp['responses'][(string)$route->getLabel('sdk.response.code', '500')] = [ + 'description' => $model->getName(), + 'content' => [ + $produces => [ + 'schema' => [ + '$ref' => '#/definitions/'.$model->getType(), + ], + ], + ], + ]; + } } if(in_array($route->getLabel('sdk.response.code', 500), [204, 301, 302, 308], true)) { @@ -216,7 +259,7 @@ class Swagger2 extends Format if ((!empty($scope))) { // && 'public' != $scope $securities = ['Project' => []]; - + foreach($route->getLabel('sdk.auth', []) as $security) { if(array_key_exists($security, $this->keys)) { $securities[$security] = []; @@ -226,7 +269,7 @@ class Swagger2 extends Format $temp['x-appwrite']['auth'] = array_slice($securities, 0, $this->authCount); $temp['security'][] = $securities; } - + $body = [ 'name' => 'payload', 'in' => 'body', @@ -399,7 +442,7 @@ class Swagger2 extends Format if($model->isAny()) { $output['definitions'][$model->getType()]['additionalProperties'] = true; } - + if(!empty($required)) { $output['definitions'][$model->getType()]['required'] = $required; } @@ -414,7 +457,7 @@ class Swagger2 extends Format case 'json': $type = 'string'; break; - + case 'integer': $type = 'integer'; $format = 'int32'; @@ -424,19 +467,40 @@ class Swagger2 extends Format $type = 'number'; $format = 'float'; break; - + + case 'double': + $type = 'number'; + $format = 'double'; + break; + case 'boolean': $type = 'boolean'; break; - + default: $type = 'object'; $rule['type'] = ($rule['type']) ? $rule['type'] : 'none'; - $items = [ - 'type' => $type, - '$ref' => '#/definitions/'.$rule['type'], - ]; + if(\is_array($rule['type'])) { + if($rule['array']) { + $items = [ + 'anyOf' => \array_map(function($type) { + return ['$ref' => '#/definitions/'.$type]; + }, $rule['type']) + ]; + } else { + $items = [ + 'oneOf' => \array_map(function($type) { + return ['$ref' => '#/definitions/'.$type]; + }, $rule['type']) + ]; + } + } else { + $items = [ + 'type' => $type, + '$ref' => '#/definitions/'.$rule['type'], + ]; + } break; } diff --git a/src/Appwrite/Utopia/Response.php b/src/Appwrite/Utopia/Response.php index 329dc86432..8258758353 100644 --- a/src/Appwrite/Utopia/Response.php +++ b/src/Appwrite/Utopia/Response.php @@ -11,6 +11,14 @@ use Appwrite\Utopia\Response\Model; use Appwrite\Utopia\Response\Model\None; use Appwrite\Utopia\Response\Model\Any; use Appwrite\Utopia\Response\Model\Attribute; +use Appwrite\Utopia\Response\Model\AttributeList; +use Appwrite\Utopia\Response\Model\AttributeString; +use Appwrite\Utopia\Response\Model\AttributeInteger; +use Appwrite\Utopia\Response\Model\AttributeFloat; +use Appwrite\Utopia\Response\Model\AttributeBoolean; +use Appwrite\Utopia\Response\Model\AttributeEmail; +use Appwrite\Utopia\Response\Model\AttributeIP; +use Appwrite\Utopia\Response\Model\AttributeURL; use Appwrite\Utopia\Response\Model\BaseList; use Appwrite\Utopia\Response\Model\Collection; use Appwrite\Utopia\Response\Model\Continent; @@ -79,13 +87,22 @@ class Response extends SwooleResponse // Database const MODEL_COLLECTION = 'collection'; const MODEL_COLLECTION_LIST = 'collectionList'; - const MODEL_ATTRIBUTE = 'attribute'; - const MODEL_ATTRIBUTE_LIST = 'attributeList'; const MODEL_INDEX = 'index'; const MODEL_INDEX_LIST = 'indexList'; const MODEL_DOCUMENT = 'document'; const MODEL_DOCUMENT_LIST = 'documentList'; + // Database Attributes + const MODEL_ATTRIBUTE = 'attribute'; + const MODEL_ATTRIBUTE_LIST = 'attributeList'; + const MODEL_ATTRIBUTE_STRING = 'attributeString'; + const MODEL_ATTRIBUTE_INTEGER= 'attributeInteger'; + const MODEL_ATTRIBUTE_FLOAT= 'attributeFloat'; + const MODEL_ATTRIBUTE_BOOLEAN= 'attributeBoolean'; + const MODEL_ATTRIBUTE_EMAIL= 'attributeEmail'; + const MODEL_ATTRIBUTE_IP= 'attributeIp'; + const MODEL_ATTRIBUTE_URL= 'attributeUrl'; + // Users const MODEL_USER = 'user'; const MODEL_USER_LIST = 'userList'; @@ -172,7 +189,6 @@ class Response extends SwooleResponse ->setModel(new ErrorDev()) // Lists ->setModel(new BaseList('Collections List', self::MODEL_COLLECTION_LIST, 'collections', self::MODEL_COLLECTION)) - ->setModel(new BaseList('Attributes List', self::MODEL_ATTRIBUTE_LIST, 'attributes', self::MODEL_ATTRIBUTE)) ->setModel(new BaseList('Indexes List', self::MODEL_INDEX_LIST, 'indexes', self::MODEL_INDEX)) ->setModel(new BaseList('Documents List', self::MODEL_DOCUMENT_LIST, 'documents', self::MODEL_DOCUMENT)) ->setModel(new BaseList('Users List', self::MODEL_USER_LIST, 'users', self::MODEL_USER)) @@ -198,6 +214,14 @@ class Response extends SwooleResponse // Entities ->setModel(new Collection()) ->setModel(new Attribute()) + ->setModel(new AttributeList()) + ->setModel(new AttributeString()) + ->setModel(new AttributeInteger()) + ->setModel(new AttributeFloat()) + ->setModel(new AttributeBoolean()) + ->setModel(new AttributeEmail()) + ->setModel(new AttributeIP()) + ->setModel(new AttributeURL()) ->setModel(new Index()) ->setModel(new ModelDocument()) ->setModel(new Log()) @@ -329,7 +353,7 @@ class Response extends SwooleResponse $document = $model->filter($document); foreach ($model->getRules() as $key => $rule) { - if (!$document->isSet($key)) { + if (!$document->isSet($key) && $rule['require']) { // do not set attribute in response if not required if (!is_null($rule['default'])) { $document->setAttribute($key, $rule['default']); } else { @@ -344,15 +368,33 @@ class Response extends SwooleResponse foreach ($data[$key] as &$item) { if ($item instanceof Document) { - if (!array_key_exists($rule['type'], $this->models)) { - throw new Exception('Missing model for rule: '. $rule['type']); + if (\is_array($rule['type'])) { + foreach ($rule['type'] as $type) { + $condition = false; + foreach ($this->getModel($type)->conditions as $attribute => $val) { + $condition = $item->getAttribute($attribute) === $val; + if(!$condition) { + break; + } + } + if ($condition) { + $ruleType = $type; + break; + } + } + } else { + $ruleType = $rule['type']; } - $item = $this->output($item, $rule['type']); + if (!array_key_exists($ruleType, $this->models)) { + throw new Exception('Missing model for rule: '. $ruleType); + } + + $item = $this->output($item, $ruleType); } } } - + $output[$key] = $data[$key]; } diff --git a/src/Appwrite/Utopia/Response/Model.php b/src/Appwrite/Utopia/Response/Model.php index 7c6aafdbdd..4c3143ca90 100644 --- a/src/Appwrite/Utopia/Response/Model.php +++ b/src/Appwrite/Utopia/Response/Model.php @@ -8,7 +8,7 @@ abstract class Model { const TYPE_STRING = 'string'; const TYPE_INTEGER = 'integer'; - const TYPE_FLOAT = 'float'; + const TYPE_FLOAT = 'double'; const TYPE_BOOLEAN = 'boolean'; const TYPE_JSON = 'json'; @@ -35,7 +35,7 @@ abstract class Model /** * Filter Document Structure * - * @return string + * @return Document */ public function filter(Document $document): Document { @@ -68,6 +68,10 @@ abstract class Model /** * Add a New Rule + * If rule is an array of documents with varying models + * + * @param string $key + * @param array $options */ protected function addRule(string $key, array $options): self { @@ -77,7 +81,7 @@ abstract class Model 'description' => '', 'default' => null, 'example' => '', - 'array' => false, + 'array' => false ], $options); return $this; diff --git a/src/Appwrite/Utopia/Response/Model/Attribute.php b/src/Appwrite/Utopia/Response/Model/Attribute.php index 145cfa057b..281a37a733 100644 --- a/src/Appwrite/Utopia/Response/Model/Attribute.php +++ b/src/Appwrite/Utopia/Response/Model/Attribute.php @@ -28,12 +28,6 @@ class Attribute extends Model 'default' => '', 'example' => 'available', ]) - ->addRule('size', [ - 'type' => self::TYPE_STRING, - 'description' => 'Attribute size.', - 'default' => 0, - 'example' => 128, - ]) ->addRule('required', [ 'type' => self::TYPE_BOOLEAN, 'description' => 'Is attribute required?', @@ -45,11 +39,13 @@ class Attribute extends Model 'description' => 'Is attribute an array?', 'default' => false, 'example' => false, - 'required' => false + 'require' => false ]) ; } + public array $conditions = []; + /** * Get Name * @@ -69,4 +65,4 @@ class Attribute extends Model { return Response::MODEL_ATTRIBUTE; } -} \ No newline at end of file +} diff --git a/src/Appwrite/Utopia/Response/Model/AttributeBoolean.php b/src/Appwrite/Utopia/Response/Model/AttributeBoolean.php new file mode 100644 index 0000000000..de7ae58130 --- /dev/null +++ b/src/Appwrite/Utopia/Response/Model/AttributeBoolean.php @@ -0,0 +1,49 @@ +addRule('default', [ + 'type' => self::TYPE_BOOLEAN, + 'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.', + 'default' => null, + 'example' => false, + 'array' => false, + 'require' => false, + ]) + ; + } + + public array $conditions = [ + 'type' => self::TYPE_BOOLEAN + ]; + + /** + * Get Name + * + * @return string + */ + public function getName():string + { + return 'AttributeBoolean'; + } + + /** + * Get Type + * + * @return string + */ + public function getType():string + { + return Response::MODEL_ATTRIBUTE_BOOLEAN; + } +} \ No newline at end of file diff --git a/src/Appwrite/Utopia/Response/Model/AttributeEmail.php b/src/Appwrite/Utopia/Response/Model/AttributeEmail.php new file mode 100644 index 0000000000..268412a557 --- /dev/null +++ b/src/Appwrite/Utopia/Response/Model/AttributeEmail.php @@ -0,0 +1,58 @@ +addRule('format', [ + 'type' => self::TYPE_STRING, + 'description' => 'String format.', + 'default' => APP_DATABASE_ATTRIBUTE_EMAIL, + 'example' => APP_DATABASE_ATTRIBUTE_EMAIL, + 'array' => false, + 'require' => true, + ]) + ->addRule('default', [ + 'type' => self::TYPE_STRING, + 'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.', + 'default' => null, + 'example' => 'default@example.com', + 'array' => false, + 'require' => false, + ]) + ; + } + + public array $conditions = [ + 'type' => self::TYPE_STRING, + 'format' => \APP_DATABASE_ATTRIBUTE_EMAIL + ]; + + /** + * Get Name + * + * @return string + */ + public function getName():string + { + return 'AttributeEmail'; + } + + /** + * Get Type + * + * @return string + */ + public function getType():string + { + return Response::MODEL_ATTRIBUTE_EMAIL; + } +} \ No newline at end of file diff --git a/src/Appwrite/Utopia/Response/Model/AttributeFloat.php b/src/Appwrite/Utopia/Response/Model/AttributeFloat.php new file mode 100644 index 0000000000..93033f5b0a --- /dev/null +++ b/src/Appwrite/Utopia/Response/Model/AttributeFloat.php @@ -0,0 +1,65 @@ +addRule('min', [ + 'type' => self::TYPE_FLOAT, + 'description' => 'Minimum value to enforce for new documents.', + 'default' => null, + 'example' => 1.5, + 'array' => false, + 'require' => false, + ]) + ->addRule('max', [ + 'type' => self::TYPE_FLOAT, + 'description' => 'Maximum value to enforce for new documents.', + 'default' => null, + 'example' => 10.5, + 'array' => false, + 'require' => false, + ]) + ->addRule('default', [ + 'type' => self::TYPE_FLOAT, + 'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.', + 'default' => null, + 'example' => 2.5, + 'array' => false, + 'require' => false, + ]) + ; + } + + public array $conditions = [ + 'type' => self::TYPE_FLOAT, + ]; + + /** + * Get Name + * + * @return string + */ + public function getName():string + { + return 'AttributeFloat'; + } + + /** + * Get Type + * + * @return string + */ + public function getType():string + { + return Response::MODEL_ATTRIBUTE_FLOAT; + } +} \ No newline at end of file diff --git a/src/Appwrite/Utopia/Response/Model/AttributeIP.php b/src/Appwrite/Utopia/Response/Model/AttributeIP.php new file mode 100644 index 0000000000..2b8f64dede --- /dev/null +++ b/src/Appwrite/Utopia/Response/Model/AttributeIP.php @@ -0,0 +1,58 @@ +addRule('format', [ + 'type' => self::TYPE_STRING, + 'description' => 'String format.', + 'default' => APP_DATABASE_ATTRIBUTE_IP, + 'example' => APP_DATABASE_ATTRIBUTE_IP, + 'array' => false, + 'require' => true, + ]) + ->addRule('default', [ + 'type' => self::TYPE_STRING, + 'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.', + 'default' => null, + 'example' => '192.0.2.0', + 'array' => false, + 'require' => false, + ]) + ; + } + + public array $conditions = [ + 'type' => self::TYPE_STRING, + 'format' => \APP_DATABASE_ATTRIBUTE_IP + ]; + + /** + * Get Name + * + * @return string + */ + public function getName():string + { + return 'AttributeIP'; + } + + /** + * Get Type + * + * @return string + */ + public function getType():string + { + return Response::MODEL_ATTRIBUTE_IP; + } +} \ No newline at end of file diff --git a/src/Appwrite/Utopia/Response/Model/AttributeInteger.php b/src/Appwrite/Utopia/Response/Model/AttributeInteger.php new file mode 100644 index 0000000000..9ff94c8f56 --- /dev/null +++ b/src/Appwrite/Utopia/Response/Model/AttributeInteger.php @@ -0,0 +1,64 @@ +addRule('min', [ + 'type' => self::TYPE_INTEGER, + 'description' => 'Minimum value to enforce for new documents.', + 'default' => null, + 'example' => 1, + 'array' => false, + 'require' => false, + ]) + ->addRule('max', [ + 'type' => self::TYPE_INTEGER, + 'description' => 'Maximum value to enforce for new documents.', + 'default' => null, + 'example' => 10, + 'array' => false, + 'require' => false, + ]) + ->addRule('default', [ + 'type' => self::TYPE_INTEGER, + 'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.', + 'default' => null, + 'example' => 10, + 'array' => false, + 'require' => false, + ]) + ; + } + + public array $conditions = [ + 'type' => self::TYPE_INTEGER, + ]; + + /** + * Get Name * + * @return string + */ + public function getName():string + { + return 'AttributeInteger'; + } + + /** + * Get Type + * + * @return string + */ + public function getType():string + { + return Response::MODEL_ATTRIBUTE_INTEGER; + } +} \ No newline at end of file diff --git a/src/Appwrite/Utopia/Response/Model/AttributeList.php b/src/Appwrite/Utopia/Response/Model/AttributeList.php new file mode 100644 index 0000000000..0cf67b3bd2 --- /dev/null +++ b/src/Appwrite/Utopia/Response/Model/AttributeList.php @@ -0,0 +1,56 @@ +addRule('sum', [ + 'type' => self::TYPE_INTEGER, + 'description' => 'Total sum of items in the list.', + 'default' => 0, + 'example' => 5, + ]) + ->addRule('attributes', [ + 'type' => [ + Response::MODEL_ATTRIBUTE_BOOLEAN, + Response::MODEL_ATTRIBUTE_INTEGER, + Response::MODEL_ATTRIBUTE_FLOAT, + Response::MODEL_ATTRIBUTE_EMAIL, + Response::MODEL_ATTRIBUTE_URL, + Response::MODEL_ATTRIBUTE_IP, + Response::MODEL_ATTRIBUTE_STRING // needs to be last, since its condition would dominate any other string attribute + ], + 'description' => 'List of attributes.', + 'default' => [], + 'array' => true + ]) + ; + } + + /** + * Get Name + * + * @return string + */ + public function getName():string + { + return 'Attributes List'; + } + + /** + * Get Type + * + * @return string + */ + public function getType():string + { + return Response::MODEL_ATTRIBUTE_LIST; + } +} \ No newline at end of file diff --git a/src/Appwrite/Utopia/Response/Model/AttributeString.php b/src/Appwrite/Utopia/Response/Model/AttributeString.php new file mode 100644 index 0000000000..e7ea853260 --- /dev/null +++ b/src/Appwrite/Utopia/Response/Model/AttributeString.php @@ -0,0 +1,55 @@ +addRule('size', [ + 'type' => self::TYPE_STRING, + 'description' => 'Attribute size.', + 'default' => 0, + 'example' => 128, + ]) + ->addRule('default', [ + 'type' => self::TYPE_STRING, + 'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.', + 'default' => null, + 'example' => 'default', + 'array' => false, + 'require' => false, + ]) + ; + } + + public array $conditions = [ + 'type' => self::TYPE_STRING, + ]; + + /** + * Get Name + * + * @return string + */ + public function getName():string + { + return 'AttributeString'; + } + + /** + * Get Type + * + * @return string + */ + public function getType():string + { + return Response::MODEL_ATTRIBUTE_STRING; + } +} \ No newline at end of file diff --git a/src/Appwrite/Utopia/Response/Model/AttributeURL.php b/src/Appwrite/Utopia/Response/Model/AttributeURL.php new file mode 100644 index 0000000000..489e038851 --- /dev/null +++ b/src/Appwrite/Utopia/Response/Model/AttributeURL.php @@ -0,0 +1,58 @@ +addRule('format', [ + 'type' => self::TYPE_STRING, + 'description' => 'String format.', + 'default' => APP_DATABASE_ATTRIBUTE_URL, + 'example' => APP_DATABASE_ATTRIBUTE_URL, + 'array' => false, + 'required' => true, + ]) + ->addRule('default', [ + 'type' => self::TYPE_STRING, + 'description' => 'Default value for attribute when not provided. Cannot be set when attribute is required.', + 'default' => null, + 'example' => 'http://example.com', + 'array' => false, + 'require' => false, + ]) + ; + } + + public array $conditions = [ + 'type' => self::TYPE_STRING, + 'format' => \APP_DATABASE_ATTRIBUTE_URL + ]; + + /** + * Get Name + * + * @return string + */ + public function getName():string + { + return 'AttributeURL'; + } + + /** + * Get Type + * + * @return string + */ + public function getType():string + { + return Response::MODEL_ATTRIBUTE_URL; + } +} \ No newline at end of file diff --git a/src/Appwrite/Utopia/Response/Model/Collection.php b/src/Appwrite/Utopia/Response/Model/Collection.php index 398bcf770f..e46603fd9a 100644 --- a/src/Appwrite/Utopia/Response/Model/Collection.php +++ b/src/Appwrite/Utopia/Response/Model/Collection.php @@ -4,7 +4,7 @@ namespace Appwrite\Utopia\Response\Model; use Appwrite\Utopia\Response; use Appwrite\Utopia\Response\Model; -use stdClass; +use Utopia\Database\Document; class Collection extends Model { @@ -44,11 +44,19 @@ class Collection extends Model 'example' => 'document', ]) ->addRule('attributes', [ - 'type' => Response::MODEL_ATTRIBUTE, + 'type' => [ + Response::MODEL_ATTRIBUTE_BOOLEAN, + Response::MODEL_ATTRIBUTE_INTEGER, + Response::MODEL_ATTRIBUTE_FLOAT, + Response::MODEL_ATTRIBUTE_EMAIL, + Response::MODEL_ATTRIBUTE_URL, + Response::MODEL_ATTRIBUTE_IP, + Response::MODEL_ATTRIBUTE_STRING, // needs to be last, since its condition would dominate any other string attribute + ], 'description' => 'Collection attributes.', 'default' => [], 'example' => new stdClass, - 'array' => true + 'array' => true, ]) ->addRule('indexes', [ 'type' => Response::MODEL_INDEX, diff --git a/tests/e2e/Services/Database/DatabaseBase.php b/tests/e2e/Services/Database/DatabaseBase.php index 33ced61da4..f0c3d4e7b3 100644 --- a/tests/e2e/Services/Database/DatabaseBase.php +++ b/tests/e2e/Services/Database/DatabaseBase.php @@ -73,7 +73,6 @@ trait DatabaseBase $this->assertEquals($releaseYear['headers']['status-code'], 201); $this->assertEquals($releaseYear['body']['key'], 'releaseYear'); $this->assertEquals($releaseYear['body']['type'], 'integer'); - $this->assertEquals($releaseYear['body']['size'], 0); $this->assertEquals($releaseYear['body']['required'], true); $this->assertEquals($actors['headers']['status-code'], 201); @@ -101,6 +100,420 @@ trait DatabaseBase return $data; } + /** + * @depends testCreateAttributes + */ + public function testAttributeResponseModels(array $data): array + { + $collection= $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' => 'Response Models', + 'read' => ['role:all'], + 'write' => ['role:all'], + 'permission' => 'document', + ]); + + $this->assertEquals($collection['headers']['status-code'], 201); + $this->assertEquals($collection['body']['name'], 'Response Models'); + + $collectionId = $collection['body']['$id']; + + $string = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/attributes/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'attributeId' => 'string', + 'size' => 16, + 'required' => false, + 'default' => 'default', + ]); + + $email = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/attributes/email', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'attributeId' => 'email', + 'required' => false, + 'default' => 'default@example.com', + ]); + + $ip = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/attributes/ip', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'attributeId' => 'ip', + 'required' => false, + 'default' => '192.0.2.0', + ]); + + $url = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/attributes/url', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'attributeId' => 'url', + 'required' => false, + 'default' => 'http://example.com', + ]); + + $integer = $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' => 'integer', + 'required' => false, + 'min' => 1, + 'max' => 5, + 'default' => 3 + ]); + + $float = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/attributes/float', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'attributeId' => 'float', + 'required' => false, + 'min' => 1.5, + 'max' => 5.5, + 'default' => 3.5 + ]); + + $boolean = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/attributes/boolean', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'attributeId' => 'boolean', + 'required' => false, + 'default' => true, + ]); + + $this->assertEquals(201, $string['headers']['status-code']); + $this->assertEquals('string', $string['body']['key']); + $this->assertEquals('string', $string['body']['type']); + $this->assertEquals('processing', $string['body']['status']); + $this->assertEquals(false, $string['body']['required']); + $this->assertEquals(false, $string['body']['array']); + $this->assertEquals(16, $string['body']['size']); + $this->assertEquals('default', $string['body']['default']); + + $this->assertEquals(201, $email['headers']['status-code']); + $this->assertEquals('email', $email['body']['key']); + $this->assertEquals('string', $email['body']['type']); + $this->assertEquals('processing', $email['body']['status']); + $this->assertEquals(false, $email['body']['required']); + $this->assertEquals(false, $email['body']['array']); + $this->assertEquals('email', $email['body']['format']); + $this->assertEquals('default@example.com', $email['body']['default']); + + $this->assertEquals(201, $ip['headers']['status-code']); + $this->assertEquals('ip', $ip['body']['key']); + $this->assertEquals('string', $ip['body']['type']); + $this->assertEquals('processing', $ip['body']['status']); + $this->assertEquals(false, $ip['body']['required']); + $this->assertEquals(false, $ip['body']['array']); + $this->assertEquals('ip', $ip['body']['format']); + $this->assertEquals('192.0.2.0', $ip['body']['default']); + + $this->assertEquals(201, $url['headers']['status-code']); + $this->assertEquals('url', $url['body']['key']); + $this->assertEquals('string', $url['body']['type']); + $this->assertEquals('processing', $url['body']['status']); + $this->assertEquals(false, $url['body']['required']); + $this->assertEquals(false, $url['body']['array']); + $this->assertEquals('url', $url['body']['format']); + $this->assertEquals('http://example.com', $url['body']['default']); + + $this->assertEquals(201, $integer['headers']['status-code']); + $this->assertEquals('integer', $integer['body']['key']); + $this->assertEquals('integer', $integer['body']['type']); + $this->assertEquals('processing', $integer['body']['status']); + $this->assertEquals(false, $integer['body']['required']); + $this->assertEquals(false, $integer['body']['array']); + $this->assertEquals(1, $integer['body']['min']); + $this->assertEquals(5, $integer['body']['max']); + $this->assertEquals(3, $integer['body']['default']); + + $this->assertEquals(201, $float['headers']['status-code']); + $this->assertEquals('float', $float['body']['key']); + $this->assertEquals('double', $float['body']['type']); + $this->assertEquals('processing', $float['body']['status']); + $this->assertEquals(false, $float['body']['required']); + $this->assertEquals(false, $float['body']['array']); + $this->assertEquals(1.5, $float['body']['min']); + $this->assertEquals(5.5, $float['body']['max']); + $this->assertEquals(3.5, $float['body']['default']); + + $this->assertEquals(201, $boolean['headers']['status-code']); + $this->assertEquals('boolean', $boolean['body']['key']); + $this->assertEquals('boolean', $boolean['body']['type']); + $this->assertEquals('processing', $boolean['body']['status']); + $this->assertEquals(false, $boolean['body']['required']); + $this->assertEquals(false, $boolean['body']['array']); + $this->assertEquals(true, $boolean['body']['default']); + + // wait for database worker to create attributes + sleep(5); + + $stringResponse = $this->client->call(Client::METHOD_GET, "/database/collections/{$collectionId}/attributes/{$collectionId}_{$string['body']['key']}",array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $emailResponse = $this->client->call(Client::METHOD_GET, "/database/collections/{$collectionId}/attributes/{$collectionId}_{$email['body']['key']}",array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $ipResponse = $this->client->call(Client::METHOD_GET, "/database/collections/{$collectionId}/attributes/{$collectionId}_{$ip['body']['key']}",array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $urlResponse = $this->client->call(Client::METHOD_GET, "/database/collections/{$collectionId}/attributes/{$collectionId}_{$url['body']['key']}",array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $integerResponse = $this->client->call(Client::METHOD_GET, "/database/collections/{$collectionId}/attributes/{$collectionId}_{$integer['body']['key']}",array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $floatResponse = $this->client->call(Client::METHOD_GET, "/database/collections/{$collectionId}/attributes/{$collectionId}_{$float['body']['key']}",array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $booleanResponse = $this->client->call(Client::METHOD_GET, "/database/collections/{$collectionId}/attributes/{$collectionId}_{$boolean['body']['key']}",array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(200, $stringResponse['headers']['status-code']); + $this->assertEquals($string['body']['key'], $stringResponse['body']['key']); + $this->assertEquals($string['body']['type'], $stringResponse['body']['type']); + $this->assertEquals('available', $stringResponse['body']['status']); + $this->assertEquals($string['body']['required'], $stringResponse['body']['required']); + $this->assertEquals($string['body']['array'], $stringResponse['body']['array']); + $this->assertEquals(16, $stringResponse['body']['size']); + $this->assertEquals($string['body']['default'], $stringResponse['body']['default']); + + $this->assertEquals(200, $emailResponse['headers']['status-code']); + $this->assertEquals($email['body']['key'], $emailResponse['body']['key']); + $this->assertEquals($email['body']['type'], $emailResponse['body']['type']); + $this->assertEquals('available', $emailResponse['body']['status']); + $this->assertEquals($email['body']['required'], $emailResponse['body']['required']); + $this->assertEquals($email['body']['array'], $emailResponse['body']['array']); + $this->assertEquals($email['body']['format'], $emailResponse['body']['format']); + $this->assertEquals($email['body']['default'], $emailResponse['body']['default']); + + $this->assertEquals(200, $ipResponse['headers']['status-code']); + $this->assertEquals($ip['body']['key'], $ipResponse['body']['key']); + $this->assertEquals($ip['body']['type'], $ipResponse['body']['type']); + $this->assertEquals('available', $ipResponse['body']['status']); + $this->assertEquals($ip['body']['required'], $ipResponse['body']['required']); + $this->assertEquals($ip['body']['array'], $ipResponse['body']['array']); + $this->assertEquals($ip['body']['format'], $ipResponse['body']['format']); + $this->assertEquals($ip['body']['default'], $ipResponse['body']['default']); + + $this->assertEquals(200, $urlResponse['headers']['status-code']); + $this->assertEquals($url['body']['key'], $urlResponse['body']['key']); + $this->assertEquals($url['body']['type'], $urlResponse['body']['type']); + $this->assertEquals('available', $urlResponse['body']['status']); + $this->assertEquals($url['body']['required'], $urlResponse['body']['required']); + $this->assertEquals($url['body']['array'], $urlResponse['body']['array']); + $this->assertEquals($url['body']['format'], $urlResponse['body']['format']); + $this->assertEquals($url['body']['default'], $urlResponse['body']['default']); + + $this->assertEquals(200, $integerResponse['headers']['status-code']); + $this->assertEquals($integer['body']['key'], $integerResponse['body']['key']); + $this->assertEquals($integer['body']['type'], $integerResponse['body']['type']); + $this->assertEquals('available', $integerResponse['body']['status']); + $this->assertEquals($integer['body']['required'], $integerResponse['body']['required']); + $this->assertEquals($integer['body']['array'], $integerResponse['body']['array']); + $this->assertEquals($integer['body']['min'], $integerResponse['body']['min']); + $this->assertEquals($integer['body']['max'], $integerResponse['body']['max']); + $this->assertEquals($integer['body']['default'], $integerResponse['body']['default']); + + $this->assertEquals(200, $floatResponse['headers']['status-code']); + $this->assertEquals($float['body']['key'], $floatResponse['body']['key']); + $this->assertEquals($float['body']['type'], $floatResponse['body']['type']); + $this->assertEquals('available', $floatResponse['body']['status']); + $this->assertEquals($float['body']['required'], $floatResponse['body']['required']); + $this->assertEquals($float['body']['array'], $floatResponse['body']['array']); + $this->assertEquals($float['body']['min'], $floatResponse['body']['min']); + $this->assertEquals($float['body']['max'], $floatResponse['body']['max']); + $this->assertEquals($float['body']['default'], $floatResponse['body']['default']); + + $this->assertEquals(200, $booleanResponse['headers']['status-code']); + $this->assertEquals($boolean['body']['key'], $booleanResponse['body']['key']); + $this->assertEquals($boolean['body']['type'], $booleanResponse['body']['type']); + $this->assertEquals('available', $booleanResponse['body']['status']); + $this->assertEquals($boolean['body']['required'], $booleanResponse['body']['required']); + $this->assertEquals($boolean['body']['array'], $booleanResponse['body']['array']); + $this->assertEquals($boolean['body']['default'], $booleanResponse['body']['default']); + + $attributes = $this->client->call(Client::METHOD_GET, '/database/collections/' . $collectionId . '/attributes', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(200, $attributes['headers']['status-code']); + $this->assertEquals(7, $attributes['body']['sum']); + + $attributes = $attributes['body']['attributes']; + + $this->assertIsArray($attributes); + $this->assertCount(7, $attributes); + + $this->assertEquals($stringResponse['body']['key'], $attributes[0]['key']); + $this->assertEquals($stringResponse['body']['type'], $attributes[0]['type']); + $this->assertEquals($stringResponse['body']['status'], $attributes[0]['status']); + $this->assertEquals($stringResponse['body']['required'], $attributes[0]['required']); + $this->assertEquals($stringResponse['body']['array'], $attributes[0]['array']); + $this->assertEquals($stringResponse['body']['size'], $attributes[0]['size']); + $this->assertEquals($stringResponse['body']['default'], $attributes[0]['default']); + + $this->assertEquals($emailResponse['body']['key'], $attributes[1]['key']); + $this->assertEquals($emailResponse['body']['type'], $attributes[1]['type']); + $this->assertEquals($emailResponse['body']['status'], $attributes[1]['status']); + $this->assertEquals($emailResponse['body']['required'], $attributes[1]['required']); + $this->assertEquals($emailResponse['body']['array'], $attributes[1]['array']); + $this->assertEquals($emailResponse['body']['default'], $attributes[1]['default']); + $this->assertEquals($emailResponse['body']['format'], $attributes[1]['format']); + + $this->assertEquals($ipResponse['body']['key'], $attributes[2]['key']); + $this->assertEquals($ipResponse['body']['type'], $attributes[2]['type']); + $this->assertEquals($ipResponse['body']['status'], $attributes[2]['status']); + $this->assertEquals($ipResponse['body']['required'], $attributes[2]['required']); + $this->assertEquals($ipResponse['body']['array'], $attributes[2]['array']); + $this->assertEquals($ipResponse['body']['default'], $attributes[2]['default']); + $this->assertEquals($ipResponse['body']['format'], $attributes[2]['format']); + + $this->assertEquals($urlResponse['body']['key'], $attributes[3]['key']); + $this->assertEquals($urlResponse['body']['type'], $attributes[3]['type']); + $this->assertEquals($urlResponse['body']['status'], $attributes[3]['status']); + $this->assertEquals($urlResponse['body']['required'], $attributes[3]['required']); + $this->assertEquals($urlResponse['body']['array'], $attributes[3]['array']); + $this->assertEquals($urlResponse['body']['default'], $attributes[3]['default']); + $this->assertEquals($urlResponse['body']['format'], $attributes[3]['format']); + + $this->assertEquals($integerResponse['body']['key'], $attributes[4]['key']); + $this->assertEquals($integerResponse['body']['type'], $attributes[4]['type']); + $this->assertEquals($integerResponse['body']['status'], $attributes[4]['status']); + $this->assertEquals($integerResponse['body']['required'], $attributes[4]['required']); + $this->assertEquals($integerResponse['body']['array'], $attributes[4]['array']); + $this->assertEquals($integerResponse['body']['default'], $attributes[4]['default']); + $this->assertEquals($integerResponse['body']['min'], $attributes[4]['min']); + $this->assertEquals($integerResponse['body']['max'], $attributes[4]['max']); + + $this->assertEquals($floatResponse['body']['key'], $attributes[5]['key']); + $this->assertEquals($floatResponse['body']['type'], $attributes[5]['type']); + $this->assertEquals($floatResponse['body']['status'], $attributes[5]['status']); + $this->assertEquals($floatResponse['body']['required'], $attributes[5]['required']); + $this->assertEquals($floatResponse['body']['array'], $attributes[5]['array']); + $this->assertEquals($floatResponse['body']['default'], $attributes[5]['default']); + $this->assertEquals($floatResponse['body']['min'], $attributes[5]['min']); + $this->assertEquals($floatResponse['body']['max'], $attributes[5]['max']); + + $this->assertEquals($booleanResponse['body']['key'], $attributes[6]['key']); + $this->assertEquals($booleanResponse['body']['type'], $attributes[6]['type']); + $this->assertEquals($booleanResponse['body']['status'], $attributes[6]['status']); + $this->assertEquals($booleanResponse['body']['required'], $attributes[6]['required']); + $this->assertEquals($booleanResponse['body']['array'], $attributes[6]['array']); + $this->assertEquals($booleanResponse['body']['default'], $attributes[6]['default']); + + $collection = $this->client->call(Client::METHOD_GET, '/database/collections/' . $collectionId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(200, $collection['headers']['status-code']); + + $attributes = $collection['body']['attributes']; + + $this->assertIsArray($attributes); + $this->assertCount(7, $attributes); + + $this->assertEquals($stringResponse['body']['key'], $attributes[0]['key']); + $this->assertEquals($stringResponse['body']['type'], $attributes[0]['type']); + $this->assertEquals($stringResponse['body']['status'], $attributes[0]['status']); + $this->assertEquals($stringResponse['body']['required'], $attributes[0]['required']); + $this->assertEquals($stringResponse['body']['array'], $attributes[0]['array']); + $this->assertEquals($stringResponse['body']['size'], $attributes[0]['size']); + $this->assertEquals($stringResponse['body']['default'], $attributes[0]['default']); + + $this->assertEquals($emailResponse['body']['key'], $attributes[1]['key']); + $this->assertEquals($emailResponse['body']['type'], $attributes[1]['type']); + $this->assertEquals($emailResponse['body']['status'], $attributes[1]['status']); + $this->assertEquals($emailResponse['body']['required'], $attributes[1]['required']); + $this->assertEquals($emailResponse['body']['array'], $attributes[1]['array']); + $this->assertEquals($emailResponse['body']['default'], $attributes[1]['default']); + $this->assertEquals($emailResponse['body']['format'], $attributes[1]['format']); + + $this->assertEquals($ipResponse['body']['key'], $attributes[2]['key']); + $this->assertEquals($ipResponse['body']['type'], $attributes[2]['type']); + $this->assertEquals($ipResponse['body']['status'], $attributes[2]['status']); + $this->assertEquals($ipResponse['body']['required'], $attributes[2]['required']); + $this->assertEquals($ipResponse['body']['array'], $attributes[2]['array']); + $this->assertEquals($ipResponse['body']['default'], $attributes[2]['default']); + $this->assertEquals($ipResponse['body']['format'], $attributes[2]['format']); + + $this->assertEquals($urlResponse['body']['key'], $attributes[3]['key']); + $this->assertEquals($urlResponse['body']['type'], $attributes[3]['type']); + $this->assertEquals($urlResponse['body']['status'], $attributes[3]['status']); + $this->assertEquals($urlResponse['body']['required'], $attributes[3]['required']); + $this->assertEquals($urlResponse['body']['array'], $attributes[3]['array']); + $this->assertEquals($urlResponse['body']['default'], $attributes[3]['default']); + $this->assertEquals($urlResponse['body']['format'], $attributes[3]['format']); + + $this->assertEquals($integerResponse['body']['key'], $attributes[4]['key']); + $this->assertEquals($integerResponse['body']['type'], $attributes[4]['type']); + $this->assertEquals($integerResponse['body']['status'], $attributes[4]['status']); + $this->assertEquals($integerResponse['body']['required'], $attributes[4]['required']); + $this->assertEquals($integerResponse['body']['array'], $attributes[4]['array']); + $this->assertEquals($integerResponse['body']['default'], $attributes[4]['default']); + $this->assertEquals($integerResponse['body']['min'], $attributes[4]['min']); + $this->assertEquals($integerResponse['body']['max'], $attributes[4]['max']); + + $this->assertEquals($floatResponse['body']['key'], $attributes[5]['key']); + $this->assertEquals($floatResponse['body']['type'], $attributes[5]['type']); + $this->assertEquals($floatResponse['body']['status'], $attributes[5]['status']); + $this->assertEquals($floatResponse['body']['required'], $attributes[5]['required']); + $this->assertEquals($floatResponse['body']['array'], $attributes[5]['array']); + $this->assertEquals($floatResponse['body']['default'], $attributes[5]['default']); + $this->assertEquals($floatResponse['body']['min'], $attributes[5]['min']); + $this->assertEquals($floatResponse['body']['max'], $attributes[5]['max']); + + $this->assertEquals($booleanResponse['body']['key'], $attributes[6]['key']); + $this->assertEquals($booleanResponse['body']['type'], $attributes[6]['type']); + $this->assertEquals($booleanResponse['body']['status'], $attributes[6]['status']); + $this->assertEquals($booleanResponse['body']['required'], $attributes[6]['required']); + $this->assertEquals($booleanResponse['body']['array'], $attributes[6]['array']); + $this->assertEquals($booleanResponse['body']['default'], $attributes[6]['default']); + + return $data; + } + /** * @depends testCreateAttributes */