From 4f86cb527eea3a011da9b0c1b29b27a254e78316 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 23 Sep 2021 21:54:21 -0400 Subject: [PATCH 01/16] Fix composer file broken during merge --- composer.json | 1 + composer.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 8fc148e656..c51501f712 100644 --- a/composer.json +++ b/composer.json @@ -83,3 +83,4 @@ "php": "8.0" } } +} diff --git a/composer.lock b/composer.lock index 4fc89ea245..8c6a8e88b8 100644 --- a/composer.lock +++ b/composer.lock @@ -2576,16 +2576,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 +2653,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 +2661,7 @@ "type": "github" } ], - "time": "2021-07-16T20:06:06+00:00" + "time": "2021-09-23T18:43:08+00:00" }, { "name": "amphp/byte-stream", From 2c2b5f6232a4a8bbfa3e642a3da3f0a44c1b7a97 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 27 Sep 2021 20:26:33 -0400 Subject: [PATCH 02/16] Create docs for api/sdk description --- app/controllers/api/database.php | 14 +++++++------- .../database/create-boolean-attribute.md | 1 + docs/references/database/create-email-attribute.md | 1 + docs/references/database/create-float-attribute.md | 1 + .../database/create-integer-attribute.md | 1 + docs/references/database/create-ip-attribute.md | 1 + .../references/database/create-string-attribute.md | 1 + docs/references/database/create-url-attribute.md | 1 + 8 files changed, 14 insertions(+), 7 deletions(-) create mode 100644 docs/references/database/create-boolean-attribute.md create mode 100644 docs/references/database/create-email-attribute.md create mode 100644 docs/references/database/create-float-attribute.md create mode 100644 docs/references/database/create-integer-attribute.md create mode 100644 docs/references/database/create-ip-attribute.md create mode 100644 docs/references/database/create-string-attribute.md create mode 100644 docs/references/database/create-url-attribute.md diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 90a745eb0a..9e75c0ae11 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -666,7 +666,7 @@ App::post('/v1/database/collections/:collectionId/attributes/string') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'database') ->label('sdk.method', 'createStringAttribute') - ->label('sdk.description', '/docs/references/database/create-attribute-string.md') + ->label('sdk.description', '/docs/references/database/create-string-attribute.md') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_ATTRIBUTE) @@ -714,7 +714,7 @@ App::post('/v1/database/collections/:collectionId/attributes/email') ->label('sdk.namespace', 'database') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.method', 'createEmailAttribute') - ->label('sdk.description', '/docs/references/database/create-attribute-email.md') + ->label('sdk.description', '/docs/references/database/create-email-attribute.md') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_ATTRIBUTE) @@ -756,7 +756,7 @@ App::post('/v1/database/collections/:collectionId/attributes/ip') ->label('sdk.namespace', 'database') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.method', 'createIpAttribute') - ->label('sdk.description', '/docs/references/database/create-attribute-ip.md') + ->label('sdk.description', '/docs/references/database/create-ip-attribute.md') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_ATTRIBUTE) @@ -798,7 +798,7 @@ App::post('/v1/database/collections/:collectionId/attributes/url') ->label('sdk.namespace', 'database') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.method', 'createUrlAttribute') - ->label('sdk.description', '/docs/references/database/create-attribute-url.md') + ->label('sdk.description', '/docs/references/database/create-url-attribute.md') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_ATTRIBUTE) @@ -840,7 +840,7 @@ App::post('/v1/database/collections/:collectionId/attributes/integer') ->label('sdk.namespace', 'database') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.method', 'createIntegerAttribute') - ->label('sdk.description', '/docs/references/database/create-attribute-integer.md') + ->label('sdk.description', '/docs/references/database/create-integer-attribute.md') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_ATTRIBUTE) @@ -904,7 +904,7 @@ App::post('/v1/database/collections/:collectionId/attributes/float') ->label('sdk.namespace', 'database') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.method', 'createFloatAttribute') - ->label('sdk.description', '/docs/references/database/create-attribute-float.md') + ->label('sdk.description', '/docs/references/database/create-float-attribute.md') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_ATTRIBUTE) @@ -968,7 +968,7 @@ App::post('/v1/database/collections/:collectionId/attributes/boolean') ->label('sdk.namespace', 'database') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.method', 'createBooleanAttribute') - ->label('sdk.description', '/docs/references/database/create-attribute-boolean.md') + ->label('sdk.description', '/docs/references/database/create-boolean-attribute.md') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_ATTRIBUTE) diff --git a/docs/references/database/create-boolean-attribute.md b/docs/references/database/create-boolean-attribute.md new file mode 100644 index 0000000000..91c5449deb --- /dev/null +++ b/docs/references/database/create-boolean-attribute.md @@ -0,0 +1 @@ +Create a boolean attribute. diff --git a/docs/references/database/create-email-attribute.md b/docs/references/database/create-email-attribute.md new file mode 100644 index 0000000000..7dd93d5a80 --- /dev/null +++ b/docs/references/database/create-email-attribute.md @@ -0,0 +1 @@ +Create an email attribute. diff --git a/docs/references/database/create-float-attribute.md b/docs/references/database/create-float-attribute.md new file mode 100644 index 0000000000..00ad538d84 --- /dev/null +++ b/docs/references/database/create-float-attribute.md @@ -0,0 +1 @@ +Create a float attribute. Optionally, minimum and maximum values can be provided. diff --git a/docs/references/database/create-integer-attribute.md b/docs/references/database/create-integer-attribute.md new file mode 100644 index 0000000000..d5455ff5f9 --- /dev/null +++ b/docs/references/database/create-integer-attribute.md @@ -0,0 +1 @@ +Create an integer attribute. Optionally, minimum and maximum values can be provided. diff --git a/docs/references/database/create-ip-attribute.md b/docs/references/database/create-ip-attribute.md new file mode 100644 index 0000000000..b0fa02ef76 --- /dev/null +++ b/docs/references/database/create-ip-attribute.md @@ -0,0 +1 @@ +Create IP address attribute. diff --git a/docs/references/database/create-string-attribute.md b/docs/references/database/create-string-attribute.md new file mode 100644 index 0000000000..b17fb1ce5a --- /dev/null +++ b/docs/references/database/create-string-attribute.md @@ -0,0 +1 @@ +Create a new string attribute. diff --git a/docs/references/database/create-url-attribute.md b/docs/references/database/create-url-attribute.md new file mode 100644 index 0000000000..1b9c55dd46 --- /dev/null +++ b/docs/references/database/create-url-attribute.md @@ -0,0 +1 @@ +Create a URL attribute. From 2aa94e7897a14ac272fc2826e8a9a5b248c86136 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 4 Oct 2021 20:23:15 -0400 Subject: [PATCH 03/16] Improve attr/index lifecycle with status stuck when deletion fails --- app/controllers/api/database.php | 16 ++++++++++++---- app/workers/database.php | 8 ++++---- src/Appwrite/Utopia/Response/Model/Attribute.php | 2 +- src/Appwrite/Utopia/Response/Model/Index.php | 2 +- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 054a586059..ee076ef2fa 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -81,7 +81,7 @@ function createAttribute($collectionId, $attribute, $response, $dbForInternal, $ 'key' => $attributeId, 'collectionId' => $collectionId, 'type' => $type, - 'status' => 'processing', // processing, available, failed, deleting + 'status' => 'processing', // processing, available, failed, deleting, stuck 'size' => $size, 'required' => $required, 'signed' => $signed, @@ -1136,7 +1136,11 @@ App::delete('/v1/database/collections/:collectionId/attributes/:attributeId') throw new Exception('Attribute not found', 404); } - $attribute = $dbForInternal->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'deleting')); + // Only update status if removing available attribute + if ($attribute->getAttribute('status' === 'available')) { + $attribute = $dbForInternal->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'deleting')); + } + $dbForInternal->purgeDocument('collections', $collectionId); $database @@ -1233,7 +1237,7 @@ App::post('/v1/database/collections/:collectionId/indexes') $index = $dbForInternal->createDocument('indexes', new Document([ '$id' => $collectionId.'_'.$indexId, 'key' => $indexId, - 'status' => 'processing', // processing, available, failed, deleting + 'status' => 'processing', // processing, available, failed, deleting, stuck 'collectionId' => $collectionId, 'type' => $type, 'attributes' => $attributes, @@ -1388,7 +1392,11 @@ App::delete('/v1/database/collections/:collectionId/indexes/:indexId') throw new Exception('Index not found', 404); } - $index = $dbForInternal->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'deleting')); + // Only update status if removing available index + if ($index->getAttribute('status') === 'available') { + $index = $dbForInternal->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'deleting')); + } + $dbForInternal->purgeDocument('collections', $collectionId); $database diff --git a/app/workers/database.php b/app/workers/database.php index 2366b37998..8fb781dc2f 100644 --- a/app/workers/database.php +++ b/app/workers/database.php @@ -110,14 +110,14 @@ class DatabaseV1 extends Worker $key = $attribute->getAttribute('key', ''); try { - if(!$dbForExternal->deleteAttribute($collectionId, $key)) { + if(!$dbForExternal->deleteAttribute($collectionId, $key) && $attribute->getAttribute('status') !== 'failed') { throw new Exception('Failed to delete Attribute'); } $dbForInternal->deleteDocument('attributes', $attribute->getId()); } catch (\Throwable $th) { Console::error($th->getMessage()); - $dbForInternal->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'failed')); + $dbForInternal->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'stuck')); } $dbForInternal->purgeDocument('collections', $collectionId); @@ -167,14 +167,14 @@ class DatabaseV1 extends Worker $key = $index->getAttribute('key'); try { - if(!$dbForExternal->deleteIndex($collectionId, $key)) { + if(!$dbForExternal->deleteIndex($collectionId, $key) && $index->getAttribute('status') !== 'failed') { throw new Exception('Failed to delete Attribute'); } $dbForInternal->deleteDocument('indexes', $index->getId()); } catch (\Throwable $th) { Console::error($th->getMessage()); - $dbForInternal->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'failed')); + $dbForInternal->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'stuck')); } $dbForInternal->purgeDocument('collections', $collectionId); diff --git a/src/Appwrite/Utopia/Response/Model/Attribute.php b/src/Appwrite/Utopia/Response/Model/Attribute.php index 281a37a733..7d064ecb1b 100644 --- a/src/Appwrite/Utopia/Response/Model/Attribute.php +++ b/src/Appwrite/Utopia/Response/Model/Attribute.php @@ -24,7 +24,7 @@ class Attribute extends Model ]) ->addRule('status', [ 'type' => self::TYPE_STRING, - 'description' => 'Attribute status. Possible values: `available`, `processing`, `deleting`, or `failed`', + 'description' => 'Attribute status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`', 'default' => '', 'example' => 'available', ]) diff --git a/src/Appwrite/Utopia/Response/Model/Index.php b/src/Appwrite/Utopia/Response/Model/Index.php index 4314971e39..d2d00fb196 100644 --- a/src/Appwrite/Utopia/Response/Model/Index.php +++ b/src/Appwrite/Utopia/Response/Model/Index.php @@ -24,7 +24,7 @@ class Index extends Model ]) ->addRule('status', [ 'type' => self::TYPE_STRING, - 'description' => 'Index status. Possible values: `available`, `processing`, `deleting`, or `failed`', + 'description' => 'Index status. Possible values: `available`, `processing`, `deleting`, `stuck`, or `failed`', 'default' => '', 'example' => 'available', ]) From d57f7b0a30a11bce2faa06b3e68640f090547bfe Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 4 Oct 2021 20:43:27 -0400 Subject: [PATCH 04/16] Throw exception on index creation if any attribute is not ready --- app/controllers/api/database.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index ee076ef2fa..bb1a86a6d9 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -1217,7 +1217,6 @@ App::post('/v1/database/collections/:collectionId/indexes') // lengths hidden by default $lengths = []; - // set attribute size as length for strings, null otherwise foreach ($attributes as $key => $attribute) { // find attribute metadata in collection document $attributeIndex = \array_search($attribute, array_column($oldAttributes, 'key')); @@ -1226,10 +1225,16 @@ App::post('/v1/database/collections/:collectionId/indexes') throw new Exception('Unknown attribute: ' . $attribute, 400); } + $attributeStatus = $oldAttributes[$attributeIndex]['status']; $attributeType = $oldAttributes[$attributeIndex]['type']; $attributeSize = $oldAttributes[$attributeIndex]['size']; - // Only set length for indexes on strings + // ensure attribute is available + if ($attributeStatus !== 'available') { + throw new Exception ('Attribute not available: ' . $oldAttributes[$attributeIndex]['key'], 400); + } + + // set attribute size as index length only for strings $lengths[$key] = ($attributeType === Database::VAR_STRING) ? $attributeSize : null; } From 1f031f1187f8f56155ecca9beaaf63ba162992aa Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 7 Oct 2021 14:44:14 -0400 Subject: [PATCH 05/16] Remove legacy method --- app/controllers/api/database.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 8e29d0593f..47703a5269 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -1142,7 +1142,6 @@ 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 From ffbcc728bbe2ca9c6d16afa3d7e01d9a8ea07fad Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 25 Oct 2021 16:00:33 -0400 Subject: [PATCH 06/16] support integers as valid float values --- composer.json | 2 +- composer.lock | 41 ++++++++-------- tests/e2e/Services/Database/DatabaseBase.php | 51 +++++++++++++++----- 3 files changed, 60 insertions(+), 34 deletions(-) diff --git a/composer.json b/composer.json index 031f8db033..8b0d69f1fe 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,7 @@ "appwrite/php-clamav": "1.1.*", "appwrite/php-runtimes": "0.6.*", - "utopia-php/framework": "0.18.*", + "utopia-php/framework": "0.19.*", "utopia-php/abuse": "0.6.*", "utopia-php/analytics": "0.2.*", "utopia-php/audit": "0.6.*", diff --git a/composer.lock b/composer.lock index 0c8cb7edb7..9ad7258756 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": "e55db0e0bb8929027e77cb9a9164a215", + "content-hash": "fa378feaffc446f557a140035a1c77b6", "packages": [ { "name": "adhocore/jwt", @@ -613,16 +613,16 @@ }, { "name": "guzzlehttp/promises", - "version": "1.5.0", + "version": "1.5.1", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "136a635e2b4a49b9d79e9c8fee267ffb257fdba0" + "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/136a635e2b4a49b9d79e9c8fee267ffb257fdba0", - "reference": "136a635e2b4a49b9d79e9c8fee267ffb257fdba0", + "url": "https://api.github.com/repos/guzzle/promises/zipball/fe752aedc9fd8fcca3fe7ad05d419d32998a06da", + "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da", "shasum": "" }, "require": { @@ -677,7 +677,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/1.5.0" + "source": "https://github.com/guzzle/promises/tree/1.5.1" }, "funding": [ { @@ -693,7 +693,7 @@ "type": "tidelift" } ], - "time": "2021-10-07T13:05:22+00:00" + "time": "2021-10-22T20:56:57+00:00" }, { "name": "guzzlehttp/psr7", @@ -2255,16 +2255,16 @@ }, { "name": "utopia-php/framework", - "version": "0.18.0", + "version": "0.19.0", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "f577522a5eb8009967b893fb7ad4ee70d3f7c0db" + "reference": "c86fc078ef258f3c88d3a25233202267314df3a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/f577522a5eb8009967b893fb7ad4ee70d3f7c0db", - "reference": "f577522a5eb8009967b893fb7ad4ee70d3f7c0db", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/c86fc078ef258f3c88d3a25233202267314df3a9", + "reference": "c86fc078ef258f3c88d3a25233202267314df3a9", "shasum": "" }, "require": { @@ -2298,9 +2298,9 @@ ], "support": { "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.18.0" + "source": "https://github.com/utopia-php/framework/tree/0.19.0" }, - "time": "2021-08-19T04:58:47+00:00" + "time": "2021-10-08T11:46:20+00:00" }, { "name": "utopia-php/image", @@ -3064,16 +3064,16 @@ }, { "name": "composer/semver", - "version": "3.2.5", + "version": "3.2.6", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "31f3ea725711245195f62e54ffa402d8ef2fdba9" + "reference": "83e511e247de329283478496f7a1e114c9517506" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/31f3ea725711245195f62e54ffa402d8ef2fdba9", - "reference": "31f3ea725711245195f62e54ffa402d8ef2fdba9", + "url": "https://api.github.com/repos/composer/semver/zipball/83e511e247de329283478496f7a1e114c9517506", + "reference": "83e511e247de329283478496f7a1e114c9517506", "shasum": "" }, "require": { @@ -3125,7 +3125,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.2.5" + "source": "https://github.com/composer/semver/tree/3.2.6" }, "funding": [ { @@ -3141,7 +3141,7 @@ "type": "tidelift" } ], - "time": "2021-05-24T12:41:47+00:00" + "time": "2021-10-25T11:34:17+00:00" }, { "name": "composer/xdebug-handler", @@ -5423,7 +5423,6 @@ "type": "github" } ], - "abandoned": true, "time": "2020-09-28T06:45:17+00:00" }, { @@ -6530,5 +6529,5 @@ "platform-overrides": { "php": "8.0" }, - "plugin-api-version": "2.0.0" + "plugin-api-version": "2.1.0" } diff --git a/tests/e2e/Services/Database/DatabaseBase.php b/tests/e2e/Services/Database/DatabaseBase.php index 1f5c633f1e..e88c321067 100644 --- a/tests/e2e/Services/Database/DatabaseBase.php +++ b/tests/e2e/Services/Database/DatabaseBase.php @@ -1293,17 +1293,16 @@ trait DatabaseBase 'max' => 1.4, ]); - // TODO@kodumbeats float validator rejects 0.0 and 1.0 as floats - // $probability = $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' => 'probability', - // 'required' => false, - // 'min' => \floatval(0.0), - // 'max' => \floatval(1.0), - // ]); + $probability = $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' => 'probability', + 'required' => false, + 'min' => 0, + 'max' => 1, + ]); $upperBound = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/attributes/integer', array_merge([ 'content-type' => 'application/json', @@ -1345,6 +1344,7 @@ trait DatabaseBase $this->assertEquals(201, $url['headers']['status-code']); $this->assertEquals(201, $range['headers']['status-code']); $this->assertEquals(201, $floatRange['headers']['status-code']); + $this->assertEquals(201, $probability['headers']['status-code']); $this->assertEquals(201, $upperBound['headers']['status-code']); $this->assertEquals(201, $lowerBound['headers']['status-code']); // $this->assertEquals(400, $invalidRange['headers']['status-code']); @@ -1359,7 +1359,7 @@ trait DatabaseBase 'x-appwrite-key' => $this->getProject()['apiKey'], ]), []); - $this->assertCount(8, $collection['body']['attributes']); + $this->assertCount(9, $collection['body']['attributes']); /** * Test for successful validation @@ -1437,6 +1437,18 @@ trait DatabaseBase 'write' => ['user:'.$this->getUser()['$id']], ]); + $goodProbability = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'documentId' => 'unique()', + 'data' => [ + 'probability' => 0.99999, + ], + 'read' => ['user:'.$this->getUser()['$id']], + 'write' => ['user:'.$this->getUser()['$id']], + ]); + $notTooHigh = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/documents', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -1467,6 +1479,7 @@ trait DatabaseBase $this->assertEquals(201, $goodUrl['headers']['status-code']); $this->assertEquals(201, $goodRange['headers']['status-code']); $this->assertEquals(201, $goodFloatRange['headers']['status-code']); + $this->assertEquals(201, $goodProbability['headers']['status-code']); $this->assertEquals(201, $notTooHigh['headers']['status-code']); $this->assertEquals(201, $notTooLow['headers']['status-code']); @@ -1546,6 +1559,18 @@ trait DatabaseBase 'write' => ['user:'.$this->getUser()['$id']], ]); + $badProbability = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'documentId' => 'unique()', + 'data' => [ + 'probability' => 1.1, + ], + 'read' => ['user:'.$this->getUser()['$id']], + 'write' => ['user:'.$this->getUser()['$id']], + ]); + $tooHigh = $this->client->call(Client::METHOD_POST, '/database/collections/' . $collectionId . '/documents', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -1576,6 +1601,7 @@ trait DatabaseBase $this->assertEquals(400, $badUrl['headers']['status-code']); $this->assertEquals(400, $badRange['headers']['status-code']); $this->assertEquals(400, $badFloatRange['headers']['status-code']); + $this->assertEquals(400, $badProbability['headers']['status-code']); $this->assertEquals(400, $tooHigh['headers']['status-code']); $this->assertEquals(400, $tooLow['headers']['status-code']); $this->assertEquals('Invalid document structure: Attribute "email" has invalid format. Value must be a valid email address', $badEmail['body']['message']); @@ -1584,6 +1610,7 @@ trait DatabaseBase $this->assertEquals('Invalid document structure: Attribute "url" has invalid format. Value must be a valid URL', $badUrl['body']['message']); $this->assertEquals('Invalid document structure: Attribute "range" has invalid format. Value must be a valid range between 1 and 10', $badRange['body']['message']); $this->assertEquals('Invalid document structure: Attribute "floatRange" has invalid format. Value must be a valid range between 1 and 1', $badFloatRange['body']['message']); + $this->assertEquals('Invalid document structure: Attribute "probability" has invalid format. Value must be a valid range between 0 and 1', $badProbability['body']['message']); $this->assertEquals('Invalid document structure: Attribute "upperBound" has invalid format. Value must be a valid range between -9,223,372,036,854,775,808 and 10', $tooHigh['body']['message']); $this->assertEquals('Invalid document structure: Attribute "lowerBound" has invalid format. Value must be a valid range between 5 and 9,223,372,036,854,775,808', $tooLow['body']['message']); } From 5d0c0b28368898c9952d0c446b56c380019dcf7e Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 25 Oct 2021 16:10:39 -0400 Subject: [PATCH 07/16] Throw exception if $min > $max --- app/controllers/api/database.php | 10 +++++++++ tests/e2e/Services/Database/DatabaseBase.php | 23 ++++++++++---------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 063e814eb8..146587e86b 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -922,6 +922,11 @@ App::post('/v1/database/collections/:collectionId/attributes/integer') // Ensure attribute default is within range $min = (is_null($min)) ? PHP_INT_MIN : \intval($min); $max = (is_null($max)) ? PHP_INT_MAX : \intval($max); + + if ($min > $max) { + throw new Exception('Minimum value must be lesser than maximum value', 400); + } + $validator = new Range($min, $max, Database::VAR_INTEGER); if (!is_null($default) && !$validator->isValid($default)) { @@ -986,6 +991,11 @@ App::post('/v1/database/collections/:collectionId/attributes/float') // Ensure attribute default is within range $min = (is_null($min)) ? PHP_FLOAT_MIN : \floatval($min); $max = (is_null($max)) ? PHP_FLOAT_MAX : \floatval($max); + + if ($min > $max) { + throw new Exception('Minimum value must be lesser than maximum value', 400); + } + $validator = new Range($min, $max, Database::VAR_FLOAT); if (!is_null($default) && !$validator->isValid($default)) { diff --git a/tests/e2e/Services/Database/DatabaseBase.php b/tests/e2e/Services/Database/DatabaseBase.php index e88c321067..82e065224b 100644 --- a/tests/e2e/Services/Database/DatabaseBase.php +++ b/tests/e2e/Services/Database/DatabaseBase.php @@ -1328,16 +1328,15 @@ trait DatabaseBase * Test for failure */ - // TODO@kodumbeats troubleshoot - // $invalidRange = $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' => 'invalidRange', - // 'required' => false, - // 'min' => 4, - // 'max' => 3, - // ]); + $invalidRange = $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' => 'invalidRange', + 'required' => false, + 'min' => 4, + 'max' => 3, + ]); $this->assertEquals(201, $email['headers']['status-code']); $this->assertEquals(201, $ip['headers']['status-code']); @@ -1347,8 +1346,8 @@ trait DatabaseBase $this->assertEquals(201, $probability['headers']['status-code']); $this->assertEquals(201, $upperBound['headers']['status-code']); $this->assertEquals(201, $lowerBound['headers']['status-code']); - // $this->assertEquals(400, $invalidRange['headers']['status-code']); - // $this->assertEquals('Minimum value must be lesser than maximum value', $invalidRange['body']['message']); + $this->assertEquals(400, $invalidRange['headers']['status-code']); + $this->assertEquals('Minimum value must be lesser than maximum value', $invalidRange['body']['message']); // wait for worker to add attributes sleep(3); From c0ade69c14d3a2a6e13034b790d0c09b5fbe45a5 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 25 Oct 2021 16:15:16 -0400 Subject: [PATCH 08/16] Sleep long enough for all attributes to create --- tests/e2e/Services/Database/DatabaseCustomServerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/Services/Database/DatabaseCustomServerTest.php b/tests/e2e/Services/Database/DatabaseCustomServerTest.php index d268cb492d..a09009143c 100644 --- a/tests/e2e/Services/Database/DatabaseCustomServerTest.php +++ b/tests/e2e/Services/Database/DatabaseCustomServerTest.php @@ -688,7 +688,7 @@ class DatabaseCustomServerTest extends Scope $this->assertEquals($attribute['headers']['status-code'], 201); } - sleep(5); + sleep(10); $collection = $this->client->call(Client::METHOD_GET, '/database/collections/' . $collectionId, array_merge([ 'content-type' => 'application/json', From f30858f09eb12819cf85b297226839eec23052a0 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 25 Oct 2021 20:14:12 -0400 Subject: [PATCH 09/16] Ensure attributes are available before testing indexes --- tests/e2e/Services/Database/DatabaseCustomServerTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/e2e/Services/Database/DatabaseCustomServerTest.php b/tests/e2e/Services/Database/DatabaseCustomServerTest.php index a09009143c..3b08fd3e5a 100644 --- a/tests/e2e/Services/Database/DatabaseCustomServerTest.php +++ b/tests/e2e/Services/Database/DatabaseCustomServerTest.php @@ -703,6 +703,10 @@ class DatabaseCustomServerTest extends Scope $this->assertCount(64, $collection['body']['attributes']); $this->assertCount(0, $collection['body']['indexes']); + foreach ($collection['body']['attributes'] as $attribute) { + $this->assertEquals('available', $attribute['status']); + } + // testing for indexLimit = 64 // MariaDB, MySQL, and MongoDB create 3 indexes per new collection // Add up to the limit, then check if the next index throws IndexLimitException From c3627cac24888c36ddeea00fb434951fb86c1563 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 25 Oct 2021 20:14:55 -0400 Subject: [PATCH 10/16] Define delete types as constants --- app/init.php | 5 +++++ app/workers/deletes.php | 11 +++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/init.php b/app/init.php index 7bc2d4fa3f..a365eaf2d0 100644 --- a/app/init.php +++ b/app/init.php @@ -93,6 +93,11 @@ const DATABASE_TYPE_DELETE_ATTRIBUTE = 'deleteAttribute'; const DATABASE_TYPE_DELETE_INDEX = 'deleteIndex'; // Deletion Types const DELETE_TYPE_DOCUMENT = 'document'; +const DELETE_TYPE_COLLECTIONS = 'collections'; +const DELETE_TYPE_PROJECTS = 'projects'; +const DELETE_TYPE_FUNCTIONS = 'functions'; +const DELETE_TYPE_USERS = 'users'; +const DELETE_TYPE_TEAMS= 'teams'; const DELETE_TYPE_EXECUTIONS = 'executions'; const DELETE_TYPE_AUDIT = 'audit'; const DELETE_TYPE_ABUSE = 'abuse'; diff --git a/app/workers/deletes.php b/app/workers/deletes.php index f507a72bbc..bd663836a3 100644 --- a/app/workers/deletes.php +++ b/app/workers/deletes.php @@ -38,20 +38,19 @@ class DeletesV1 extends Worker $document = new Document($document); switch ($document->getCollection()) { - // TODO@kodumbeats define these as constants somewhere - case 'collections': + case DELETE_TYPE_COLLECTIONS: $this->deleteCollection($document, $projectId); break; - case 'projects': + case DELETE_TYPE_PROJECTS: $this->deleteProject($document); break; - case 'functions': + case DELETE_TYPE_FUNCTIONS: $this->deleteFunction($document, $projectId); break; - case 'users': + case DELETE_TYPE_USERS: $this->deleteUser($document, $projectId); break; - case 'teams': + case DELETE_TYPE_TEAMS: $this->deleteMemberships($document, $projectId); break; default: From 353eb2b057e75fd2520401b61a1cbcd54df6676f Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 25 Oct 2021 21:12:27 -0400 Subject: [PATCH 11/16] Respond with proper model on deleteAttribute --- app/controllers/api/database.php | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 146587e86b..6693bc37f1 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -1219,8 +1219,26 @@ App::delete('/v1/database/collections/:collectionId/attributes/:attributeId') $usage->setParam('database.collections.update', 1); + // 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_ENUM => Response::MODEL_ATTRIBUTE_ENUM, + 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, + }; + $events - ->setParam('payload', $response->output($attribute, Response::MODEL_ATTRIBUTE)) + ->setParam('payload', $response->output($attribute, $model)) ; $audits From 549db830862190ab17910de9cf7c1872da12ad32 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 25 Oct 2021 21:14:30 -0400 Subject: [PATCH 12/16] Test webhooks for deleting attributes and indexes --- app/workers/deletes.php | 1 - tests/e2e/Services/Webhooks/WebhooksBase.php | 34 +++++++++++++++++-- .../Webhooks/WebhooksCustomServerTest.php | 31 ++++++++--------- 3 files changed, 46 insertions(+), 20 deletions(-) diff --git a/app/workers/deletes.php b/app/workers/deletes.php index bd663836a3..fcc080ad43 100644 --- a/app/workers/deletes.php +++ b/app/workers/deletes.php @@ -290,7 +290,6 @@ class DeletesV1 extends Worker { Authorization::disable(); - // TODO@kodumbeats is it better to pass objects or ID strings? if($database->deleteDocument($document->getCollection(), $document->getId())) { Console::success('Deleted document "'.$document->getId().'" successfully'); diff --git a/tests/e2e/Services/Webhooks/WebhooksBase.php b/tests/e2e/Services/Webhooks/WebhooksBase.php index 35cb032133..a4d1f98e2a 100644 --- a/tests/e2e/Services/Webhooks/WebhooksBase.php +++ b/tests/e2e/Services/Webhooks/WebhooksBase.php @@ -72,10 +72,22 @@ trait WebhooksBase 'required' => true, ]); + $extra = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/attributes/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'attributeId' => 'extra', + 'size' => 64, + 'required' => false, + ]); + $this->assertEquals($firstName['headers']['status-code'], 201); $this->assertEquals($firstName['body']['key'], 'firstName'); $this->assertEquals($lastName['headers']['status-code'], 201); $this->assertEquals($lastName['body']['key'], 'lastName'); + $this->assertEquals($extra['headers']['status-code'], 201); + $this->assertEquals($extra['body']['key'], 'extra'); // wait for database worker to kick in sleep(10); @@ -90,9 +102,27 @@ trait WebhooksBase $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); $this->assertNotEmpty($webhook['data']['key']); - $this->assertEquals($webhook['data']['key'], 'lastName'); + $this->assertEquals($webhook['data']['key'], 'extra'); - // TODO@kodumbeats test webhook for removing attribute + $removed = $this->client->call(Client::METHOD_DELETE, '/database/collections/' . $data['actorsId'] . '/attributes/' . $extra['body']['key'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(204, $removed['headers']['status-code']); + + $webhook = $this->getLastRequest(); + + // $this->assertEquals($webhook['method'], 'DELETE'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'database.attributes.delete'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertNotEmpty($webhook['data']['key']); + $this->assertEquals($webhook['data']['key'], 'extra'); return $data; } diff --git a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php index bcd6e33f9d..09aaa63524 100644 --- a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php +++ b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php @@ -87,27 +87,24 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), true); - // TODO@kodumbeats test for indexes.delete // Remove index - // $index = $this->client->call(Client::METHOD_DELETE, '/database/collections/' . $data['actorsId'] . '/indexes/' . $index['body']['$id'], array_merge([ - // 'content-type' => 'application/json', - // 'x-appwrite-project' => $this->getProject()['$id'], - // 'x-appwrite-key' => $this->getProject()['apiKey'] - // ])); + $this->client->call(Client::METHOD_DELETE, '/database/collections/' . $data['actorsId'] . '/indexes/' . $index['body']['key'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); // // wait for database worker to remove index - // sleep(5); + $webhook = $this->getLastRequest(); - // $webhook = $this->getLastRequest(); - - // // $this->assertEquals($webhook['method'], 'DELETE'); - // $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); - // $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - // $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'database.indexes.delete'); - // $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); - // $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); - // $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); - // $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), true); + // $this->assertEquals($webhook['method'], 'DELETE'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'database.indexes.delete'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], 'not-yet-implemented'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); + $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), true); return $data; } From cb6a21f34f8153286dd9e3f845eb8c397312ce16 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Tue, 26 Oct 2021 15:15:46 -0400 Subject: [PATCH 13/16] avoid overwhelming DatabaseV1 queue with >1000 attributes at once --- .../Database/DatabaseCustomServerTest.php | 89 ++++++++++--------- 1 file changed, 49 insertions(+), 40 deletions(-) diff --git a/tests/e2e/Services/Database/DatabaseCustomServerTest.php b/tests/e2e/Services/Database/DatabaseCustomServerTest.php index 3b08fd3e5a..fba1b09c16 100644 --- a/tests/e2e/Services/Database/DatabaseCustomServerTest.php +++ b/tests/e2e/Services/Database/DatabaseCustomServerTest.php @@ -558,50 +558,59 @@ class DatabaseCustomServerTest extends Scope $this->assertEquals($response['headers']['status-code'], 404); } - public function testAttributeCountLimit() - { - $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' => 'attributeCountLimit', - 'read' => ['role:all'], - 'write' => ['role:all'], - 'permission' => 'document', - ]); + // Adds several minutes to test to replicate coverage in Utopia\Database unit tests + // and messes with subsequent tests as DatabaseV1 queue gets overwhelmed + // TODO@kodumbeats either fix or remove testAttributeCountLimit + // Options to fix: + // - Enable attribute creation in batches + // - Use additional database workers + // - Wait for worker to complete before moving onto next test + // - Remove since this is unit tested in Utopia\Database + // + // public function testAttributeCountLimit() + // { + // $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' => 'attributeCountLimit', + // 'read' => ['role:all'], + // 'write' => ['role:all'], + // 'permission' => 'document', + // ]); - $collectionId = $collection['body']['$id']; + // $collectionId = $collection['body']['$id']; - // load the collection up to the limit - for ($i=0; $i < 1012; $i++) { - $attribute = $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' => "attribute{$i}", - 'required' => false, - ]); + // // load the collection up to the limit + // for ($i=0; $i < 1012; $i++) { + // $attribute = $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' => "attribute{$i}", + // 'required' => false, + // ]); - $this->assertEquals(201, $attribute['headers']['status-code']); - } + // $this->assertEquals(201, $attribute['headers']['status-code']); + // } - sleep(30); + // sleep(30); - $tooMany = $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' => "tooMany", - 'required' => false, - ]); + // $tooMany = $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' => "tooMany", + // 'required' => false, + // ]); - $this->assertEquals(400, $tooMany['headers']['status-code']); - $this->assertEquals('Attribute limit exceeded', $tooMany['body']['message']); - } + // $this->assertEquals(400, $tooMany['headers']['status-code']); + // $this->assertEquals('Attribute limit exceeded', $tooMany['body']['message']); + // } public function testAttributeRowWidthLimit() { @@ -688,7 +697,7 @@ class DatabaseCustomServerTest extends Scope $this->assertEquals($attribute['headers']['status-code'], 201); } - sleep(10); + sleep(20); $collection = $this->client->call(Client::METHOD_GET, '/database/collections/' . $collectionId, array_merge([ 'content-type' => 'application/json', @@ -704,7 +713,7 @@ class DatabaseCustomServerTest extends Scope $this->assertCount(0, $collection['body']['indexes']); foreach ($collection['body']['attributes'] as $attribute) { - $this->assertEquals('available', $attribute['status']); + $this->assertEquals('available', $attribute['status'], 'attribute: ' . $attribute['key']); } // testing for indexLimit = 64 From c6ee3766b7023662afb60ea05ab0e17acdcd4a88 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Tue, 26 Oct 2021 16:14:25 -0400 Subject: [PATCH 14/16] Prevent default values for array attributes --- app/controllers/api/database.php | 6 +++++- tests/e2e/Services/Database/DatabaseBase.php | 12 ++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 6693bc37f1..11adec3205 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -76,6 +76,10 @@ function createAttribute($collectionId, $attribute, $response, $dbForInternal, $ throw new Exception('Cannot set default value for required attribute', 400); } + if ($array && $default) { + throw new Exception('Cannot set default value for array attributes', 400); + } + try { $attribute = new Document([ '$id' => $collectionId.'_'.$attributeId, @@ -1591,7 +1595,7 @@ App::post('/v1/database/collections/:collectionId/documents') $usage ->setParam('database.documents.create', 1) ->setParam('collectionId', $collectionId) - ; + ; $audits ->setParam('event', 'database.documents.create') diff --git a/tests/e2e/Services/Database/DatabaseBase.php b/tests/e2e/Services/Database/DatabaseBase.php index 82e065224b..3f11154c49 100644 --- a/tests/e2e/Services/Database/DatabaseBase.php +++ b/tests/e2e/Services/Database/DatabaseBase.php @@ -1338,6 +1338,16 @@ trait DatabaseBase 'max' => 3, ]); + $defaultArray = $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' => 'defaultArray', + 'required' => false, + 'default' => 42, + 'array' => true, + ]); + $this->assertEquals(201, $email['headers']['status-code']); $this->assertEquals(201, $ip['headers']['status-code']); $this->assertEquals(201, $url['headers']['status-code']); @@ -1347,7 +1357,9 @@ trait DatabaseBase $this->assertEquals(201, $upperBound['headers']['status-code']); $this->assertEquals(201, $lowerBound['headers']['status-code']); $this->assertEquals(400, $invalidRange['headers']['status-code']); + $this->assertEquals(400, $defaultArray['headers']['status-code']); $this->assertEquals('Minimum value must be lesser than maximum value', $invalidRange['body']['message']); + $this->assertEquals('Cannot set default value for array attributes', $defaultArray['body']['message']); // wait for worker to add attributes sleep(3); From 10172f421621e88d21059c8819cff80fb0e727a2 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Tue, 26 Oct 2021 16:28:16 -0400 Subject: [PATCH 15/16] Require minimum size --- app/controllers/api/database.php | 2 +- app/init.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 11adec3205..9afbd602e1 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -677,7 +677,7 @@ App::post('/v1/database/collections/:collectionId/attributes/string') ->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.') + ->param('size', null, new Range(1, APP_DATABASE_ATTRIBUTE_STRING_MAX_LENGTH, self::TYPE_INTEGER), 'Attribute size for text attributes, in number of characters.') ->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('array', false, new Boolean(), 'Is attribute an array?', true) diff --git a/app/init.php b/app/init.php index a365eaf2d0..7d8307880a 100644 --- a/app/init.php +++ b/app/init.php @@ -70,6 +70,7 @@ 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_DATABASE_ATTRIBUTE_STRING_MAX_LENGTH = 1073741824; // 2^32 bits / 4 bits per char const APP_STORAGE_UPLOADS = '/storage/uploads'; const APP_STORAGE_FUNCTIONS = '/storage/functions'; const APP_STORAGE_CACHE = '/storage/cache'; From 0312b071cec1e13e7e6d4b706880c7d475052b02 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Tue, 26 Oct 2021 16:51:40 -0400 Subject: [PATCH 16/16] Get integer constant from Range class --- app/controllers/api/database.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 9afbd602e1..5b228d6004 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -677,7 +677,7 @@ App::post('/v1/database/collections/:collectionId/attributes/string') ->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 Range(1, APP_DATABASE_ATTRIBUTE_STRING_MAX_LENGTH, self::TYPE_INTEGER), 'Attribute size for text attributes, in number of characters.') + ->param('size', null, new Range(1, APP_DATABASE_ATTRIBUTE_STRING_MAX_LENGTH, Range::TYPE_INTEGER), 'Attribute size for text attributes, in number of characters.') ->param('required', null, new Boolean(), 'Is attribute required?') ->param('default', null, new Text(0), '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)