From 48b12152e3858dfcdea2230c8d075ef09ea82cd3 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Tue, 23 Mar 2021 17:19:19 -0400 Subject: [PATCH 01/90] Outline createIndex --- app/controllers/api/database.php | 39 ++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 2c32ae981..3e48fea24 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -283,6 +283,45 @@ App::delete('/v1/database/collections/:collectionId') $response->noContent(); }); +App::post('/v1/database/collections/:collectionId/indexes') + ->desc('Create Index') + ->groups(['api', 'database']) + ->label('event', 'database.indexes.create') + ->label('scope', 'indexes.write') + ->label('sdk.namespace', 'database') + ->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER]) + ->label('sdk.method', 'createIndex') + ->label('sdk.description', '/docs/references/database/create-index.md') + ->label('sdk.response.code', Response::STATUS_CODE_CREATED) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_INDEX) + ->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') + ->param('name', null, new Text(256), 'Index name.') + ->param('type', null, new Text(256), 'Index type.') + ->inject('response') + ->action(function ($collectionId, $name, $type, $response, $projectDB, $audits) { + /** @var Appwrite\Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Event\Event $audits */ + + try { + $data = $projectDB->createIndex($collectionId, $name, $type); + } catch (\Exception $exception) { + throw new Exception('Failed creating index', 500); + } + + $audits + ->setParam('event', 'database.indexes.create') + ->setParam('resource', 'database/indexes/'.$data['$id']) + ->setParam('data', $data->getArrayCopy()) + ; + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($data, Response::MODEL_INDEX) + ; + }); + App::post('/v1/database/collections/:collectionId/documents') ->desc('Create Document') ->groups(['api', 'database']) From 7a07ff58d702de30110df558138b93d4fa7040bf Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Wed, 24 Mar 2021 11:40:33 -0400 Subject: [PATCH 02/90] Outline index and attributes routes --- app/controllers/api/database.php | 117 ++++++++++++++++++++++++++++++- 1 file changed, 116 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 3e48fea24..afddfbfd7 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -283,13 +283,102 @@ App::delete('/v1/database/collections/:collectionId') $response->noContent(); }); +App::post('/v1/database/collections/:collectionId/attributes') + ->desc('Create Attribute') + ->groups(['api', 'database']) + ->label('event', 'database.attributes.create') + ->label('scope', 'attributes.write') + ->label('sdk.namespace', 'database') + ->label('sdk.platform', [APP_PLATFORM_SERVER]) + ->label('sdk.method', 'createAttribute') + ->label('sdk.description', '/docs/references/database/create-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) + ->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection using the Database service [server integration](/docs/server/database#createCollection).') + ->param('id', null, new Text(256), 'Attribute ID.') + ->param('type', null, new Text(256), 'Attribute type.') + ->param('size', null, new Integer(), 'Attribute size.') + ->param('array', null, new Boolean(), 'Is array flag.') + ->inject('response') + ->action(function ($collectionId, $id, $type, $size, $array, $response, $projectDB, $audits) { + /** @var Appwrite\Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Event\Event $audits */ + + try { + $data = $projectDB->createAttribute($collectionId, $id, $type, $size, $array); + } catch (\Exception $exception) { + throw new Exception('Failed creating attribute', 500); + } + + $audits + ->setParam('event', 'database.attributes.create') + ->setParam('resource', 'database/attributes/'.$data['$id']) + ->setParam('data', $data->getArrayCopy()) + ; + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($data, Response::MODEL_ATTRIBUTE) + ; + }); + +App::delete('/v1/database/collections/:collectionId/attributes/:attributeId') + ->desc('Delete Attribute') + ->groups(['api', 'database']) + ->label('scope', 'attributes.write') + ->label('event', 'database.attributes.delete') + ->label('sdk.namespace', 'database') + ->label('sdk.platform', [APP_PLATFORM_SERVER]) + ->label('sdk.method', 'deleteAttribute') + ->label('sdk.description', '/docs/references/database/delete-attribute.md') + ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) + ->label('sdk.response.model', Response::MODEL_NONE) + ->param('collectionId', '', new UID(), 'Collection unique ID.') + ->param('attributeId', '', new UID(), 'Attribute unique ID.') + ->inject('response') + ->inject('projectDB') + ->inject('events') + ->inject('audits') + ->inject('deletes') + ->action(function ($collectionId, $attributeId, $response, $projectDB, $events, $audits, $deletes) { + /** @var Appwrite\Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Event\Event $events */ + /** @var Appwrite\Event\Event $audits */ + + $attribute = $projectDB->getDocument($attributeId, false); + + if (!$projectDB->deleteAttribute($collectionId, $attributeId)) { + throw new Exception('Failed to remove attribute from DB', 500); + } + + $deletes + ->setParam('type', DELETE_TYPE_DOCUMENT) + ->setParam('document', $attribute) + ; + + $events + ->setParam('payload', $response->output($attribute, Response::MODEL_ATTRIBUTE)) + ; + + $audits + ->setParam('event', 'database.attributes.delete') + ->setParam('resource', 'database/attributes/'.$attribute->getId()) + ->setParam('data', $attribute->getArrayCopy()) + ; + + $response->noContent(); + }); + App::post('/v1/database/collections/:collectionId/indexes') ->desc('Create Index') ->groups(['api', 'database']) ->label('event', 'database.indexes.create') ->label('scope', 'indexes.write') ->label('sdk.namespace', 'database') - ->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER]) + ->label('sdk.platform', [APP_PLATFORM_SERVER]) ->label('sdk.method', 'createIndex') ->label('sdk.description', '/docs/references/database/create-index.md') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) @@ -322,6 +411,32 @@ App::post('/v1/database/collections/:collectionId/indexes') ; }); +App::get('v1/database/collections/:/collectionId/indexes') + ->desc('List Indexes') + ->groups(['api', 'database']) + ->label('scope', 'indexes.read') + ->label('sdk.namespace', 'database') + ->label('sdk.platform', [APP_PLATFORM_SERVER]) + ->label('sdk.method', 'listIndexes') + ->label('sdk.description', '/docs/references/database/list-indexes.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_INDEX_LIST) + ->inject('response') + ->inject('projectDB') + ->action(function ($response, $projectDB) { + /** @var Appwrite\Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ + + // $results = $projectDB->getIndexes([ + // ]); + + // $response->dynamic(new Document([ + // 'sum' => $projectDB->getSum(), + // 'collections' => $results + // ]), Response::MODEL_INDEX_LIST); + }); + App::post('/v1/database/collections/:collectionId/documents') ->desc('Create Document') ->groups(['api', 'database']) From c10d9b67014ec322dc3649f85185e44d9289aeb2 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 25 Mar 2021 15:52:57 -0400 Subject: [PATCH 03/90] Outline remaining routes --- app/controllers/api/database.php | 136 ++++++++++++++++++++++++++++++- 1 file changed, 133 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index afddfbfd7..0ffb66093 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -324,6 +324,60 @@ App::post('/v1/database/collections/:collectionId/attributes') ; }); +App::get('v1/database/collections/:collectionId/attributes') + ->desc('List Attributes') + ->groups(['api', 'database']) + ->label('scope', 'attributes.read') + ->label('sdk.namespace', 'database') + ->label('sdk.platform', [APP_PLATFORM_SERVER]) + ->label('sdk.method', 'listAttributes') + ->label('sdk.description', '/docs/references/database/list-attributes.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_ATTRIBUTE_LIST) + ->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') + ->inject('response') + ->inject('projectDB') + ->action(function ($collectionId, $response, $projectDB) { + /** @var Appwrite\Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ + + // $results = $projectDB->getAttributes([ + // ]); + + // $response->dynamic(new Document([ + // 'sum' => $projectDB->getSum(), + // 'collections' => $results + // ]), Response::MODEL_ATTRIBUTE_LIST); + }); + +App::get('v1/database/collections/:collectionId/attributes/:attributeId') + ->desc('Get Attribute') + ->groups(['api', 'database']) + ->label('scope', 'attributes.read') + ->label('sdk.namespace', 'database') + ->label('sdk.platform', [APP_PLATFORM_SERVER]) + ->label('sdk.method', 'listAttributes') + ->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) + ->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') + ->param('attributeId', '', new UID(), 'Attribute unique ID.') + ->inject('response') + ->inject('projectDB') + ->action(function ($collectionId, $attributeId, $response, $projectDB) { + /** @var Appwrite\Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ + + // $results = $projectDB->getAttribute([ + // ]); + + // $response->dynamic(new Document([ + // 'collections' => $results + // ]), Response::MODEL_ATTRIBUTE); + }); + App::delete('/v1/database/collections/:collectionId/attributes/:attributeId') ->desc('Delete Attribute') ->groups(['api', 'database']) @@ -335,7 +389,7 @@ App::delete('/v1/database/collections/:collectionId/attributes/:attributeId') ->label('sdk.description', '/docs/references/database/delete-attribute.md') ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.model', Response::MODEL_NONE) - ->param('collectionId', '', new UID(), 'Collection unique ID.') + ->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') ->param('attributeId', '', new UID(), 'Attribute unique ID.') ->inject('response') ->inject('projectDB') @@ -411,7 +465,7 @@ App::post('/v1/database/collections/:collectionId/indexes') ; }); -App::get('v1/database/collections/:/collectionId/indexes') +App::get('v1/database/collections/:collectionId/indexes') ->desc('List Indexes') ->groups(['api', 'database']) ->label('scope', 'indexes.read') @@ -422,6 +476,7 @@ App::get('v1/database/collections/:/collectionId/indexes') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_INDEX_LIST) + ->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') ->inject('response') ->inject('projectDB') ->action(function ($response, $projectDB) { @@ -437,6 +492,81 @@ App::get('v1/database/collections/:/collectionId/indexes') // ]), Response::MODEL_INDEX_LIST); }); +App::get('v1/database/collections/:collectionId/indexes/:indexId') + ->desc('Get Index') + ->groups(['api', 'database']) + ->label('scope', 'indexes.read') + ->label('sdk.namespace', 'database') + ->label('sdk.platform', [APP_PLATFORM_SERVER]) + ->label('sdk.method', 'listIndexes') + ->label('sdk.description', '/docs/references/database/get-index.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_INDEX) + ->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') + ->param('indexId', null, new UID(), 'Index unique ID') + ->inject('response') + ->inject('projectDB') + ->action(function ($collectionId, $indexId, $response, $projectDB) { + /** @var Appwrite\Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ + + // $results = $projectDB->getIndex([ + // ]); + + // $response->dynamic(new Document([ + // 'collections' => $results + // ]), Response::MODEL_INDEX); + }); + +App::delete('/v1/database/collections/:collectionId/indexes/:indexId') + ->desc('Delete Index') + ->groups(['api', 'database']) + ->label('scope', 'indexes.write') + ->label('event', 'database.indexes.delete') + ->label('sdk.namespace', 'database') + ->label('sdk.platform', [APP_PLATFORM_SERVER]) + ->label('sdk.method', 'deleteIndex') + ->label('sdk.description', '/docs/references/database/delete-index.md') + ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) + ->label('sdk.response.model', Response::MODEL_NONE) + ->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') + ->param('indexId', '', new UID(), 'Attribute unique ID.') + ->inject('response') + ->inject('projectDB') + ->inject('events') + ->inject('audits') + ->inject('deletes') + ->action(function ($collectionId, $indexId, $response, $projectDB, $events, $audits, $deletes) { + /** @var Appwrite\Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Event\Event $events */ + /** @var Appwrite\Event\Event $audits */ + + $index = $projectDB->getDocument($indexId, false); + + if (!$projectDB->deleteAttribute($collectionId, $indexId)) { + throw new Exception('Failed to remove attribute from DB', 500); + } + + $deletes + ->setParam('type', DELETE_TYPE_DOCUMENT) + ->setParam('document', $index) + ; + + $events + ->setParam('payload', $response->output($index, Response::MODEL_INDEX)) + ; + + $audits + ->setParam('event', 'database.indexes.delete') + ->setParam('resource', 'database/attributes/'.$index->getId()) + ->setParam('data', $index->getArrayCopy()) + ; + + $response->noContent(); + }); + App::post('/v1/database/collections/:collectionId/documents') ->desc('Create Document') ->groups(['api', 'database']) @@ -778,4 +908,4 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId') ; $response->noContent(); - }); \ No newline at end of file + }); From 6cb89ef955f6dad154cf36fc7b7f165e40d5ffb6 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Tue, 8 Jun 2021 15:30:42 -0400 Subject: [PATCH 04/90] Create response models for indexes and attributes --- src/Appwrite/Utopia/Response.php | 8 ++ .../Utopia/Response/Model/Attribute.php | 77 +++++++++++++++++++ src/Appwrite/Utopia/Response/Model/Index.php | 65 ++++++++++++++++ 3 files changed, 150 insertions(+) create mode 100644 src/Appwrite/Utopia/Response/Model/Attribute.php create mode 100644 src/Appwrite/Utopia/Response/Model/Index.php diff --git a/src/Appwrite/Utopia/Response.php b/src/Appwrite/Utopia/Response.php index cc9e174f7..65ec5230a 100644 --- a/src/Appwrite/Utopia/Response.php +++ b/src/Appwrite/Utopia/Response.php @@ -10,6 +10,7 @@ use Appwrite\Utopia\Response\Filter; 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\BaseList; use Appwrite\Utopia\Response\Model\Collection; use Appwrite\Utopia\Response\Model\Continent; @@ -22,6 +23,7 @@ use Appwrite\Utopia\Response\Model\ErrorDev; use Appwrite\Utopia\Response\Model\Execution; use Appwrite\Utopia\Response\Model\File; use Appwrite\Utopia\Response\Model\Func; +use Appwrite\Utopia\Response\Model\Index; use Appwrite\Utopia\Response\Model\JWT; use Appwrite\Utopia\Response\Model\Key; use Appwrite\Utopia\Response\Model\Language; @@ -63,6 +65,10 @@ 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_RULE = 'rule'; const MODEL_DOCUMENT = 'document'; const MODEL_DOCUMENT_LIST = 'documentList'; @@ -150,6 +156,8 @@ 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)) ->setModel(new BaseList('Sessions List', self::MODEL_SESSION_LIST, 'sessions', self::MODEL_SESSION)) diff --git a/src/Appwrite/Utopia/Response/Model/Attribute.php b/src/Appwrite/Utopia/Response/Model/Attribute.php new file mode 100644 index 000000000..523dc7acb --- /dev/null +++ b/src/Appwrite/Utopia/Response/Model/Attribute.php @@ -0,0 +1,77 @@ +addRule('$id', [ + 'type' => self::TYPE_STRING, + 'description' => 'Attribute ID.', + 'default' => '', + 'example' => '', + ]) + ->addRule('type', [ + 'type' => self::TYPE_STRING, + 'description' => 'Attribute type.', + 'default' => '', + 'example' => '', + ]) + ->addRule('size', [ + 'type' => self::TYPE_STRING, + 'description' => 'Attribute size.', + 'default' => 0, + 'example' => 0, + ]) + ->addRule('required', [ + 'type' => self::TYPE_BOOLEAN, + 'description' => 'Is attribute required?', + 'default' => false, + 'example' => false, + ]) + ->addRule('signed', [ + 'type' => self::TYPE_BOOLEAN, + 'description' => 'Is attribute signed?', + 'default' => true, + 'example' => true, + ]) + ->addRule('array', [ + 'type' => self::TYPE_BOOLEAN, + 'description' => 'Is attribute an array?', + 'default' => false, + 'example' => false, + ]) + ->addRule('filters', [ + 'type' => self::TYPE_JSON, + 'description' => 'Attribute filters.', + 'default' => [], + 'example' => [], + ]) + ; + } + + /** + * Get Name + * + * @return string + */ + public function getName():string + { + return 'Attribute'; + } + + /** + * Get Collection + * + * @return string + */ + public function getType():string + { + return Response::MODEL_ATTRIBUTE; + } +} \ No newline at end of file diff --git a/src/Appwrite/Utopia/Response/Model/Index.php b/src/Appwrite/Utopia/Response/Model/Index.php new file mode 100644 index 000000000..a3212fd8e --- /dev/null +++ b/src/Appwrite/Utopia/Response/Model/Index.php @@ -0,0 +1,65 @@ +addRule('$id', [ + 'type' => self::TYPE_STRING, + 'description' => 'Index ID.', + 'default' => '', + 'example' => '', + ]) + ->addRule('type', [ + 'type' => self::TYPE_STRING, + 'description' => 'Index type.', + 'default' => '', + 'example' => '', + ]) + ->addRule('attributes', [ + 'type' => self::TYPE_JSON, + 'description' => 'Index attributes.', + 'default' => [], + 'example' => [], + ]) + ->addRule('lengths', [ + 'type' => self::TYPE_JSON, + 'description' => 'Index lengths.', + 'default' => [], + 'example' => [], + ]) + ->addRule('orders', [ + 'type' => self::TYPE_JSON, + 'description' => 'Index orders.', + 'default' => [], + 'example' => [], + ]) + ; + } + + /** + * Get Name + * + * @return string + */ + public function getName():string + { + return 'Index'; + } + + /** + * Get Collection + * + * @return string + */ + public function getType():string + { + return Response::MODEL_INDEX; + } +} \ No newline at end of file From be79eb76c6d7cce26a5bb20e48dbaa4aad4eb157 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Tue, 8 Jun 2021 16:12:14 -0400 Subject: [PATCH 05/90] Use new db for attribute endpoints --- app/controllers/api/database.php | 100 ++++++++++++++++++------------- 1 file changed, 57 insertions(+), 43 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 9fb5d50e3..ca27cb5ce 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -17,6 +17,10 @@ use Appwrite\Database\Validator\Authorization; use Appwrite\Database\Exception\Authorization as AuthorizationException; use Appwrite\Database\Exception\Structure as StructureException; use Appwrite\Utopia\Response; +use Utopia\Database\Database as Database2; +use Utopia\Database\Document as Document2; +use Utopia\Database\Query; +use Utopia\Database\Validator\Authorization as Authorization2; App::post('/v1/database/collections') ->desc('Create Collection') @@ -297,28 +301,33 @@ App::post('/v1/database/collections/:collectionId/attributes') ->param('id', null, new Text(256), 'Attribute ID.') ->param('type', null, new Text(256), 'Attribute type.') ->param('size', null, new Integer(), 'Attribute size.') + ->param('required', null, new Boolean(), 'Is required flag.') + ->param('signed', true, new Boolean(), 'Is signed flag.') ->param('array', null, new Boolean(), 'Is array flag.') + ->param('filters', [], new ArrayList(), 'Array of filters.') ->inject('response') - ->action(function ($collectionId, $id, $type, $size, $array, $response, $projectDB, $audits) { + ->inject('dbForInternal') + ->inject('audits') + ->action(function ($collectionId, $id, $type, $size, $required, $signed, $array, $filters, $response, $dbForInternal, $audits) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal*/ /** @var Appwrite\Event\Event $audits */ - try { - $data = $projectDB->createAttribute($collectionId, $id, $type, $size, $array); - } catch (\Exception $exception) { - throw new Exception('Failed creating attribute', 500); - } + $collection = $dbForInternal->getCollection($collectionId); + + // TODO@kodumbeats find better name for var + $success = $dbForInternal->createAttribute($collectionId, $id, $type, $size, $required, $signed, $array, $filters); + + $attributes = $collection->getAttributes(); $audits ->setParam('event', 'database.attributes.create') - ->setParam('resource', 'database/attributes/'.$data['$id']) - ->setParam('data', $data->getArrayCopy()) + ->setParam('resource', 'database/attributes/'.$attributes[$id]->getId()) + ->setParam('data', $attributes[$id]) ; - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($data, Response::MODEL_ATTRIBUTE) + $response->setStatusCode(Response::STATUS_CODE_CREATED) + $response->dynamic2($attributes['$id'], Response::MODEL_ATTRIBUTE) ; }); @@ -335,18 +344,19 @@ App::get('v1/database/collections/:collectionId/attributes') ->label('sdk.response.model', Response::MODEL_ATTRIBUTE_LIST) ->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') ->inject('response') - ->inject('projectDB') - ->action(function ($collectionId, $response, $projectDB) { + ->inject('dbForInternal') + ->action(function ($collectionId, $response, $dbForInternal) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ - // $results = $projectDB->getAttributes([ - // ]); + $collection = $dbForInternal->getCollection($collectionId); - // $response->dynamic(new Document([ - // 'sum' => $projectDB->getSum(), - // 'collections' => $results - // ]), Response::MODEL_ATTRIBUTE_LIST); + $attributes = $collection->getAttributes(); + + $response->dynamic2(new Document([ + 'sum' => \count($attributes) + 'attributes' => $attributes + ]), Response::MODEL_ATTRIBUTE_LIST); }); App::get('v1/database/collections/:collectionId/attributes/:attributeId') @@ -363,17 +373,17 @@ App::get('v1/database/collections/:collectionId/attributes/:attributeId') ->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') ->param('attributeId', '', new UID(), 'Attribute unique ID.') ->inject('response') - ->inject('projectDB') - ->action(function ($collectionId, $attributeId, $response, $projectDB) { + ->inject('dbForInternal') + ->action(function ($collectionId, $attributeId, $response, $dbForInternal) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ - // $results = $projectDB->getAttribute([ - // ]); + $collection = $dbForInternal->getCollection($collectionId); - // $response->dynamic(new Document([ - // 'collections' => $results - // ]), Response::MODEL_ATTRIBUTE); + $attributes = $collection->getAttributes(); + + $response->dynamic2($attributes[$attributeId] + ), Response::MODEL_ATTRIBUTE); }); App::delete('/v1/database/collections/:collectionId/attributes/:attributeId') @@ -390,30 +400,34 @@ App::delete('/v1/database/collections/:collectionId/attributes/:attributeId') ->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') ->param('attributeId', '', new UID(), 'Attribute unique ID.') ->inject('response') - ->inject('projectDB') + ->inject('dbForInternal') ->inject('events') ->inject('audits') ->inject('deletes') - ->action(function ($collectionId, $attributeId, $response, $projectDB, $events, $audits, $deletes) { + ->action(function ($collectionId, $attributeId, $response, $dbForInternal, $events, $audits, $deletes) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForInternal */ /** @var Appwrite\Event\Event $events */ /** @var Appwrite\Event\Event $audits */ - $attribute = $projectDB->getDocument($attributeId, false); + $collection = $dbForInternal->getCollection($collectionId); - if (!$projectDB->deleteAttribute($collectionId, $attributeId)) { - throw new Exception('Failed to remove attribute from DB', 500); - } + $attributes = $collection->getAttributes(); - $deletes - ->setParam('type', DELETE_TYPE_DOCUMENT) - ->setParam('document', $attribute) - ; + $attribute = $collection[$attributeId]; - $events - ->setParam('payload', $response->output($attribute, Response::MODEL_ATTRIBUTE)) - ; + $success = $collection->deleteAttribute($collectionId, $attributeId); + + // TODO@kodumbeats use the deletes worker to handle this + // $deletes + // ->setParam('type', DELETE_TYPE_DOCUMENT) + // ->setParam('document', $attribute) + // ; + + // TODO@kodumbeats + // $events + // ->setParam('payload', $response->output($attribute, Response::MODEL_ATTRIBUTE)) + // ; $audits ->setParam('event', 'database.attributes.delete') From 44fb6f815d733820817ad011054fa3904bb96cc4 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Wed, 9 Jun 2021 17:10:19 -0400 Subject: [PATCH 06/90] Correct response models --- .../Utopia/Response/Model/Attribute.php | 12 +++++++++++- src/Appwrite/Utopia/Response/Model/Index.php | 17 ++++++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/Appwrite/Utopia/Response/Model/Attribute.php b/src/Appwrite/Utopia/Response/Model/Attribute.php index 523dc7acb..fc48d7c1e 100644 --- a/src/Appwrite/Utopia/Response/Model/Attribute.php +++ b/src/Appwrite/Utopia/Response/Model/Attribute.php @@ -10,6 +10,12 @@ class Attribute extends Model public function __construct() { $this + ->addRule('$collection', [ + 'type' => self::TYPE_STRING, + 'description' => 'Collection ID.', + 'default' => '', + 'example' => '', + ]) ->addRule('$id', [ 'type' => self::TYPE_STRING, 'description' => 'Attribute ID.', @@ -39,18 +45,22 @@ class Attribute extends Model 'description' => 'Is attribute signed?', 'default' => true, 'example' => true, + 'required' => false, ]) ->addRule('array', [ 'type' => self::TYPE_BOOLEAN, 'description' => 'Is attribute an array?', 'default' => false, 'example' => false, + 'required' => false ]) ->addRule('filters', [ - 'type' => self::TYPE_JSON, + 'type' => self::TYPE_STRING, 'description' => 'Attribute filters.', 'default' => [], 'example' => [], + 'array' => true, + 'required' => false, ]) ; } diff --git a/src/Appwrite/Utopia/Response/Model/Index.php b/src/Appwrite/Utopia/Response/Model/Index.php index a3212fd8e..f67637a26 100644 --- a/src/Appwrite/Utopia/Response/Model/Index.php +++ b/src/Appwrite/Utopia/Response/Model/Index.php @@ -10,6 +10,12 @@ class Index extends Model public function __construct() { $this + ->addRule('$collection', [ + 'type' => self::TYPE_STRING, + 'description' => 'Collection ID.', + 'default' => '', + 'example' => '', + ]) ->addRule('$id', [ 'type' => self::TYPE_STRING, 'description' => 'Index ID.', @@ -23,22 +29,27 @@ class Index extends Model 'example' => '', ]) ->addRule('attributes', [ - 'type' => self::TYPE_JSON, + 'type' => self::TYPE_STRING, 'description' => 'Index attributes.', 'default' => [], 'example' => [], + 'array' => true, ]) ->addRule('lengths', [ - 'type' => self::TYPE_JSON, + 'type' => self::TYPE_STRING, 'description' => 'Index lengths.', 'default' => [], 'example' => [], + 'array' => true, + 'required' => false, ]) ->addRule('orders', [ - 'type' => self::TYPE_JSON, + 'type' => self::TYPE_STRING, 'description' => 'Index orders.', 'default' => [], 'example' => [], + 'array' => true, + 'required' => false, ]) ; } From 6f3b331069dac6a9fe6778831aa567aad0f74e0b Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Wed, 9 Jun 2021 17:11:51 -0400 Subject: [PATCH 07/90] Align index and attribute routes to db and models --- app/controllers/api/database.php | 253 ++++++++++++++++++++----------- 1 file changed, 161 insertions(+), 92 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index ca27cb5ce..4c956d17f 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -2,6 +2,8 @@ use Utopia\App; use Utopia\Exception; +use Utopia\Validator\Boolean; +use Utopia\Validator\Numeric; use Utopia\Validator\Range; use Utopia\Validator\WhiteList; use Utopia\Validator\Text; @@ -297,38 +299,48 @@ App::post('/v1/database/collections/:collectionId/attributes') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_ATTRIBUTE) - ->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection using the Database service [server integration](/docs/server/database#createCollection).') - ->param('id', null, new Text(256), 'Attribute ID.') + ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection using the Database service [server integration](/docs/server/database#createCollection).') + ->param('id', '', new Text(256), 'Attribute ID.') ->param('type', null, new Text(256), 'Attribute type.') - ->param('size', null, new Integer(), 'Attribute size.') - ->param('required', null, new Boolean(), 'Is required flag.') - ->param('signed', true, new Boolean(), 'Is signed flag.') - ->param('array', null, new Boolean(), 'Is array flag.') + // TODO@kodumbeats add units to description + ->param('size', null, new Numeric(), 'Attribute size.') + ->param('required', null, new Boolean(), 'Is attribute required?') + ->param('signed', null, new Boolean(), 'Is attribute signed?') + ->param('array', null, new Boolean(), 'Is attribute an array?') + // TODO@kodumbeats "We should have here a whitelist of allowed values. We should add a link to a reference in the future documentation. We might be able to hide this option from the public API for now." ->param('filters', [], new ArrayList(), 'Array of filters.') ->inject('response') - ->inject('dbForInternal') + ->inject('dbForExternal') ->inject('audits') - ->action(function ($collectionId, $id, $type, $size, $required, $signed, $array, $filters, $response, $dbForInternal, $audits) { + ->action(function ($collectionId, $id, $type, $size, $required, $signed, $array, $filters, $response, $dbForExternal, $audits) { /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForInternal*/ + /** @var Utopia\Database\Database $dbForExternal*/ /** @var Appwrite\Event\Event $audits */ - $collection = $dbForInternal->getCollection($collectionId); + // TODO@kodumbeats handle failed attribute creation + $success = $dbForExternal->createAttribute($collectionId, $id, $type, $size, $required, $signed, $array, $filters); - // TODO@kodumbeats find better name for var - $success = $dbForInternal->createAttribute($collectionId, $id, $type, $size, $required, $signed, $array, $filters); - - $attributes = $collection->getAttributes(); + // Database->createAttribute() does not return a document + // So we need to create one for the response + $attribute = new Document2([ + '$collection' => $collectionId, + '$id' => $id, + 'type' => $type, + 'size' => $size, + 'required' => $required, + 'signed' => $signed, + 'array' => $array, + 'filters' => $filters + ]); $audits ->setParam('event', 'database.attributes.create') - ->setParam('resource', 'database/attributes/'.$attributes[$id]->getId()) - ->setParam('data', $attributes[$id]) + ->setParam('resource', 'database/attributes/'.$attribute->getId()) + ->setParam('data', $attribute) ; - $response->setStatusCode(Response::STATUS_CODE_CREATED) - $response->dynamic2($attributes['$id'], Response::MODEL_ATTRIBUTE) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic2($attribute, Response::MODEL_ATTRIBUTE); }); App::get('v1/database/collections/:collectionId/attributes') @@ -342,19 +354,20 @@ App::get('v1/database/collections/:collectionId/attributes') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_ATTRIBUTE_LIST) - ->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') + ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') ->inject('response') - ->inject('dbForInternal') - ->action(function ($collectionId, $response, $dbForInternal) { + ->inject('dbForExternal') + ->action(function ($collectionId, $response, $dbForExternal) { /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForInternal */ + /** @var Utopia\Database\Database $dbForExternal */ - $collection = $dbForInternal->getCollection($collectionId); + $collection = $dbForExternal->getCollection($collectionId); + // TODO@kodumbeats array_merge collectionId to each attribute $attributes = $collection->getAttributes(); - $response->dynamic2(new Document([ - 'sum' => \count($attributes) + $response->dynamic2(new Document2([ + 'sum' => \count($attributes), 'attributes' => $attributes ]), Response::MODEL_ATTRIBUTE_LIST); }); @@ -370,20 +383,30 @@ App::get('v1/database/collections/:collectionId/attributes/:attributeId') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_ATTRIBUTE) - ->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') - ->param('attributeId', '', new UID(), 'Attribute unique ID.') + ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') + ->param('attributeId', '', new Text(256), 'Attribute ID.') ->inject('response') - ->inject('dbForInternal') - ->action(function ($collectionId, $attributeId, $response, $dbForInternal) { + ->inject('dbForExternal') + ->action(function ($collectionId, $attributeId, $response, $dbForExternal) { /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForInternal */ + /** @var Utopia\Database\Database $dbForExternal */ - $collection = $dbForInternal->getCollection($collectionId); + $collection = $dbForExternal->getCollection($collectionId); $attributes = $collection->getAttributes(); - $response->dynamic2($attributes[$attributeId] - ), Response::MODEL_ATTRIBUTE); + // Search for attribute + $attributeIndex = array_search($attributeId, array_column($attributes, '$id')); + + if ($attributeIndex === false) { + throw new Exception('Attribute not found', 404); + } + + $attribute = new Document2([\array_merge($attributes[$attributeIndex], [ + 'collectionId' => $collectionId, + ])]); + + $response->dynamic2($attribute, Response::MODEL_ATTRIBUTE); }); App::delete('/v1/database/collections/:collectionId/attributes/:attributeId') @@ -397,37 +420,45 @@ App::delete('/v1/database/collections/:collectionId/attributes/:attributeId') ->label('sdk.description', '/docs/references/database/delete-attribute.md') ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.model', Response::MODEL_NONE) - ->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') - ->param('attributeId', '', new UID(), 'Attribute unique ID.') + ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') + ->param('attributeId', '', new Text(256), 'Attribute ID.') ->inject('response') - ->inject('dbForInternal') + ->inject('dbForExternal') ->inject('events') ->inject('audits') ->inject('deletes') - ->action(function ($collectionId, $attributeId, $response, $dbForInternal, $events, $audits, $deletes) { + ->action(function ($collectionId, $attributeId, $response, $dbForExternal, $events, $audits, $deletes) { /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Database $dbForInternal */ + /** @var Utopia\Database\Database $dbForExternal */ /** @var Appwrite\Event\Event $events */ /** @var Appwrite\Event\Event $audits */ - $collection = $dbForInternal->getCollection($collectionId); + $collection = $dbForExternal->getCollection($collectionId); $attributes = $collection->getAttributes(); - $attribute = $collection[$attributeId]; + // Search for attribute + $attributeIndex = array_search($attributeId, array_column($attributes, '$id')); - $success = $collection->deleteAttribute($collectionId, $attributeId); + if ($attributeIndex === false) { + throw new Exception('Attribute not found', 404); + } + + $attribute = new Document2([\array_merge($attributes[$attributeIndex], [ + 'collectionId' => $collectionId, + ])]); // TODO@kodumbeats use the deletes worker to handle this + $success = $dbForExternal->deleteAttribute($collectionId, $attributeId); + // $deletes // ->setParam('type', DELETE_TYPE_DOCUMENT) // ->setParam('document', $attribute) // ; - // TODO@kodumbeats - // $events - // ->setParam('payload', $response->output($attribute, Response::MODEL_ATTRIBUTE)) - // ; + $events + ->setParam('payload', $response->output2($attribute, Response::MODEL_ATTRIBUTE)) + ; $audits ->setParam('event', 'database.attributes.delete') @@ -450,31 +481,43 @@ App::post('/v1/database/collections/:collectionId/indexes') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_INDEX) - ->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') - ->param('name', null, new Text(256), 'Index name.') + ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') + ->param('id', null, new Text(256), 'Index ID.') + // TODO@kodumbeats type should be a whitelist of supported index types ->param('type', null, new Text(256), 'Index type.') + ->param('attributes', null, new ArrayList(new Text(256)), 'Array of attributes to index.') + ->param('lengths', null, new ArrayList(new Text(256)), 'Array of index lengths.') + ->param('orders', null, new ArrayList(new Text(256)), 'Array of index orders.') ->inject('response') - ->action(function ($collectionId, $name, $type, $response, $projectDB, $audits) { + ->inject('dbForExternal') + ->inject('audits') + ->action(function ($collectionId, $id, $type, $attributes, $lengths, $orders, $response, $dbForExternal, $audits) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForExternal */ /** @var Appwrite\Event\Event $audits */ - try { - $data = $projectDB->createIndex($collectionId, $name, $type); - } catch (\Exception $exception) { - throw new Exception('Failed creating index', 500); - } + $success = $dbForExternal->createIndex($collectionId, $id, $type, $attributes, $lengths, $orders); + + // Database->createIndex() does not return a document + // So we need to create one for the response + $index = new Document2([ + '$collection' => $collectionId, + '$id' => $id, + 'type' => $type, + 'attributes' => $attributes, + 'lengths' => $lengths, + 'orders' => $orders, + ]); $audits ->setParam('event', 'database.indexes.create') - ->setParam('resource', 'database/indexes/'.$data['$id']) - ->setParam('data', $data->getArrayCopy()) + ->setParam('resource', 'database/indexes/'.$index->getId()) + ->setParam('data', $index->getArrayCopy()) ; - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($data, Response::MODEL_INDEX) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic2($index, Response::MODEL_INDEX); + }); App::get('v1/database/collections/:collectionId/indexes') @@ -488,20 +531,22 @@ App::get('v1/database/collections/:collectionId/indexes') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_INDEX_LIST) - ->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') + ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') ->inject('response') - ->inject('projectDB') - ->action(function ($response, $projectDB) { + ->inject('dbForExternal') + ->action(function ($collectionId, $response, $dbForExternal) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForExternal */ - // $results = $projectDB->getIndexes([ - // ]); + $collection = $dbForExternal->getCollection($collectionId); - // $response->dynamic(new Document([ - // 'sum' => $projectDB->getSum(), - // 'collections' => $results - // ]), Response::MODEL_INDEX_LIST); + // TODO@kodumbeats decode index string and merge ['$collection' => $collectionId] + $indexes = $collection->getAttribute('indexes'); + + $response->dynamic2(new Document2([ + 'sum' => \count($indexes), + 'attributes' => $indexes, + ]), Response::MODEL_INDEX_LIST); }); App::get('v1/database/collections/:collectionId/indexes/:indexId') @@ -515,20 +560,31 @@ App::get('v1/database/collections/:collectionId/indexes/:indexId') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_INDEX) - ->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') - ->param('indexId', null, new UID(), 'Index unique ID') + ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') + ->param('indexId', null, new Text(256), 'Index ID.') ->inject('response') ->inject('projectDB') - ->action(function ($collectionId, $indexId, $response, $projectDB) { + ->action(function ($collectionId, $indexId, $response, $dbForExternal) { /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ - // $results = $projectDB->getIndex([ - // ]); + $collection = $dbForExternal->getCollection($collectionId); - // $response->dynamic(new Document([ - // 'collections' => $results - // ]), Response::MODEL_INDEX); + // TODO@kodumbeats decode 'indexes' into array + $indexes = $collection->getAttribute('indexes'); + + // // Search for index + $indexIndex = array_search($indexId, array_column($indexes, '$id')); + + if ($indexIndex === false) { + throw new Exception('Index not found', 404); + } + + $index = new Document2([\array_merge($indexes[$indexIndex], [ + 'collectionId' => $collectionId, + ])]); + + $response->dynamic2($index, Response::MODEL_INDEX); }); App::delete('/v1/database/collections/:collectionId/indexes/:indexId') @@ -543,36 +599,49 @@ App::delete('/v1/database/collections/:collectionId/indexes/:indexId') ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.model', Response::MODEL_NONE) ->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') - ->param('indexId', '', new UID(), 'Attribute unique ID.') + ->param('indexId', '', new UID(), 'Index unique ID.') ->inject('response') - ->inject('projectDB') + ->inject('dbForExternal') ->inject('events') ->inject('audits') ->inject('deletes') - ->action(function ($collectionId, $indexId, $response, $projectDB, $events, $audits, $deletes) { + ->action(function ($collectionId, $indexId, $response, $dbForExternal, $events, $audits, $deletes) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForExternal */ /** @var Appwrite\Event\Event $events */ /** @var Appwrite\Event\Event $audits */ - $index = $projectDB->getDocument($indexId, false); + $collection = $dbForExternal->getCollection($collectionId); - if (!$projectDB->deleteAttribute($collectionId, $indexId)) { - throw new Exception('Failed to remove attribute from DB', 500); + // TODO@kodumbeats decode 'indexes' into array + $indexes = $collection->getAttribute('indexes'); + + // // Search for index + $indexIndex = array_search($indexId, array_column($indexes, '$id')); + + if ($indexIndex === false) { + throw new Exception('Index not found', 404); } - $deletes - ->setParam('type', DELETE_TYPE_DOCUMENT) - ->setParam('document', $index) - ; + $index = new Document2([\array_merge($indexes[$indexIndex], [ + 'collectionId' => $collectionId, + ])]); + + // TODO@kodumbeats use the deletes worker to handle this + $success = $dbForExternal->deleteIndex($collectionId, $indexId); + + // $deletes + // ->setParam('type', DELETE_TYPE_DOCUMENT) + // ->setParam('document', $attribute) + // ; $events - ->setParam('payload', $response->output($index, Response::MODEL_INDEX)) + ->setParam('payload', $response->output2($index, Response::MODEL_INDEX)) ; $audits ->setParam('event', 'database.indexes.delete') - ->setParam('resource', 'database/attributes/'.$index->getId()) + ->setParam('resource', 'database/indexes/'.$index->getId()) ->setParam('data', $index->getArrayCopy()) ; From e9d93ce95e81f032f70cb247b33d7edb81472465 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 10 Jun 2021 09:15:00 -0400 Subject: [PATCH 08/90] Throw 404 when collection not found --- app/controllers/api/database.php | 36 ++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 4c956d17f..ad77af5a0 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -317,6 +317,12 @@ App::post('/v1/database/collections/:collectionId/attributes') /** @var Utopia\Database\Database $dbForExternal*/ /** @var Appwrite\Event\Event $audits */ + $collection = $dbForExternal->getCollection($collectionId); + + if (empty($collection)) { + throw new Exception('Collection not found', 404); + } + // TODO@kodumbeats handle failed attribute creation $success = $dbForExternal->createAttribute($collectionId, $id, $type, $size, $required, $signed, $array, $filters); @@ -363,6 +369,10 @@ App::get('v1/database/collections/:collectionId/attributes') $collection = $dbForExternal->getCollection($collectionId); + if (empty($collection)) { + throw new Exception('Collection not found', 404); + } + // TODO@kodumbeats array_merge collectionId to each attribute $attributes = $collection->getAttributes(); @@ -393,6 +403,10 @@ App::get('v1/database/collections/:collectionId/attributes/:attributeId') $collection = $dbForExternal->getCollection($collectionId); + if (empty($collection)) { + throw new Exception('Collection not found', 404); + } + $attributes = $collection->getAttributes(); // Search for attribute @@ -435,6 +449,10 @@ App::delete('/v1/database/collections/:collectionId/attributes/:attributeId') $collection = $dbForExternal->getCollection($collectionId); + if (empty($collection)) { + throw new Exception('Collection not found', 404); + } + $attributes = $collection->getAttributes(); // Search for attribute @@ -496,6 +514,12 @@ App::post('/v1/database/collections/:collectionId/indexes') /** @var Utopia\Database\Database $dbForExternal */ /** @var Appwrite\Event\Event $audits */ + $collection = $dbForExternal->getCollection($collectionId); + + if (empty($collection)) { + throw new Exception('Collection not found', 404); + } + $success = $dbForExternal->createIndex($collectionId, $id, $type, $attributes, $lengths, $orders); // Database->createIndex() does not return a document @@ -540,6 +564,10 @@ App::get('v1/database/collections/:collectionId/indexes') $collection = $dbForExternal->getCollection($collectionId); + if (empty($collection)) { + throw new Exception('Collection not found', 404); + } + // TODO@kodumbeats decode index string and merge ['$collection' => $collectionId] $indexes = $collection->getAttribute('indexes'); @@ -570,6 +598,10 @@ App::get('v1/database/collections/:collectionId/indexes/:indexId') $collection = $dbForExternal->getCollection($collectionId); + if (empty($collection)) { + throw new Exception('Collection not found', 404); + } + // TODO@kodumbeats decode 'indexes' into array $indexes = $collection->getAttribute('indexes'); @@ -613,6 +645,10 @@ App::delete('/v1/database/collections/:collectionId/indexes/:indexId') $collection = $dbForExternal->getCollection($collectionId); + if (empty($collection)) { + throw new Exception('Collection not found', 404); + } + // TODO@kodumbeats decode 'indexes' into array $indexes = $collection->getAttribute('indexes'); From 2c7e65af683e0f987308c35fa298e17e3bdb619e Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 10 Jun 2021 10:53:39 -0400 Subject: [PATCH 09/90] Update utopia-php/database to 0.3 --- composer.json | 2 +- composer.lock | 20 +++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/composer.json b/composer.json index c5cf7a271..8e46eccb4 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": "0.2.*", + "utopia-php/database": "^0.3.0", "utopia-php/locale": "0.3.*", "utopia-php/registry": "0.4.*", "utopia-php/preloader": "0.2.*", diff --git a/composer.lock b/composer.lock index b2d89a1e6..6069475d8 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": "5cf39daf305902a168233757d4a00066", + "content-hash": "854dfbc035c52bb373210e5d31271c65", "packages": [ { "name": "adhocore/jwt", @@ -1919,16 +1919,16 @@ }, { "name": "utopia-php/database", - "version": "0.2.0", + "version": "0.3.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "b5dd144d582f3355c13f5430b1b3d7eb850bc5cd" + "reference": "c5f9599d933f493ca69e57d34d903939425662f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/b5dd144d582f3355c13f5430b1b3d7eb850bc5cd", - "reference": "b5dd144d582f3355c13f5430b1b3d7eb850bc5cd", + "url": "https://api.github.com/repos/utopia-php/database/zipball/c5f9599d933f493ca69e57d34d903939425662f8", + "reference": "c5f9599d933f493ca69e57d34d903939425662f8", "shasum": "" }, "require": { @@ -1941,7 +1941,9 @@ "utopia-php/framework": "0.*.*" }, "require-dev": { + "fakerphp/faker": "^1.14", "phpunit/phpunit": "^9.4", + "utopia-php/cli": "^0.11.0", "vimeo/psalm": "4.0.1" }, "type": "library", @@ -1974,9 +1976,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.2.0" + "source": "https://github.com/utopia-php/database/tree/0.3.0" }, - "time": "2021-05-26T18:41:44+00:00" + "time": "2021-06-09T12:39:37+00:00" }, { "name": "utopia-php/domains", @@ -6142,5 +6144,5 @@ "platform-overrides": { "php": "8.0" }, - "plugin-api-version": "2.0.0" -} \ No newline at end of file + "plugin-api-version": "2.1.0" +} From 2d974edce9943f3c6ee2119f592017bf06839e48 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 10 Jun 2021 14:19:10 -0400 Subject: [PATCH 10/90] Update document and collection response models --- .../Utopia/Response/Model/Collection.php | 63 ++++++++++++------- .../Utopia/Response/Model/Document.php | 22 ++++--- 2 files changed, 55 insertions(+), 30 deletions(-) diff --git a/src/Appwrite/Utopia/Response/Model/Collection.php b/src/Appwrite/Utopia/Response/Model/Collection.php index 9b1f184b9..725267c0a 100644 --- a/src/Appwrite/Utopia/Response/Model/Collection.php +++ b/src/Appwrite/Utopia/Response/Model/Collection.php @@ -14,38 +14,55 @@ class Collection extends Model 'type' => self::TYPE_STRING, 'description' => 'Collection ID.', 'default' => '', - 'example' => '5e5ea5c16897e', + 'example' => '', ]) - ->addRule('$permissions', [ - 'type' => Response::MODEL_PERMISSIONS, - 'description' => 'Collection permissions.', - 'default' => new \stdClass, - 'example' => new \stdClass, - 'array' => false, + ->addRule('$read', [ + 'type' => self::TYPE_STRING, + 'description' => 'Collection read permissions.', + 'default' => '', + 'example' => '', + 'array' => true + ]) + ->addRule('$write', [ + 'type' => self::TYPE_STRING, + 'description' => 'Collection write permissions.', + 'default' => '', + 'example' => '', + 'array' => true ]) ->addRule('name', [ 'type' => self::TYPE_STRING, 'description' => 'Collection name.', 'default' => '', - 'example' => 'Movies', + 'example' => '', ]) - ->addRule('dateCreated', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Collection creation date in Unix timestamp.', - 'default' => 0, - 'example' => 1592981250, + ->addRule('attributes', [ + 'type' => self::TYPE_STRING, + 'description' => 'Collection attributes.', + 'default' => '', + 'example' => '', + 'array' => true ]) - ->addRule('dateUpdated', [ - 'type' => self::TYPE_INTEGER, - 'description' => 'Collection creation date in Unix timestamp.', - 'default' => 0, - 'example' => 1592981550, + ->addRule('indexes', [ + 'type' => self::TYPE_STRING, + 'description' => 'Collection indexes.', + 'default' => '', + 'example' => '', + 'array' => true ]) - ->addRule('rules', [ - 'type' => Response::MODEL_RULE, - 'description' => 'Collection rules.', - 'default' => [], - 'array' => true, + ->addRule('attributesInQueue', [ + 'type' => self::TYPE_STRING, + 'description' => 'Collection attributes in creation queue.', + 'default' => '', + 'example' => '', + 'array' => true + ]) + ->addRule('indexesInQueue', [ + 'type' => self::TYPE_STRING, + 'description' => 'Collection indexes in creation queue.', + 'default' => '', + 'example' => '', + 'array' => true ]) ; } diff --git a/src/Appwrite/Utopia/Response/Model/Document.php b/src/Appwrite/Utopia/Response/Model/Document.php index a8a9f8097..ba9092137 100644 --- a/src/Appwrite/Utopia/Response/Model/Document.php +++ b/src/Appwrite/Utopia/Response/Model/Document.php @@ -41,12 +41,20 @@ class Document extends Any 'default' => '', 'example' => '5e5ea5c15117e', ]) - ->addRule('$permissions', [ - 'type' => Response::MODEL_PERMISSIONS, - 'description' => 'Document permissions.', - 'default' => new \stdClass, - 'example' => new \stdClass, - 'array' => false, - ]); + ->addRule('$read', [ + 'type' => self::TYPE_STRING, + 'description' => 'Document read permissions.', + 'default' => '', + 'example' => '', + 'array' => true, + ]) + ->addRule('$write', [ + 'type' => self::TYPE_STRING, + 'description' => 'Document write permissions.', + 'default' => '', + 'example' => '', + 'array' => true, + ]) + ; } } From 7138dbd34f7fb0f8bfe908c781d96ba234e9cb2b Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 10 Jun 2021 14:19:42 -0400 Subject: [PATCH 11/90] Use new db for collections and documents --- app/controllers/api/database.php | 259 ++++++++++--------------------- 1 file changed, 81 insertions(+), 178 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index ad77af5a0..f2949322e 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -36,63 +36,26 @@ App::post('/v1/database/collections') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_COLLECTION) - ->param('name', '', new Text(128), 'Collection name. Max length: 128 chars.') - ->param('read', [], new ArrayList(new Text(64)), 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.') - ->param('write', [], new ArrayList(new Text(64)), 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.') - ->param('rules', [], function ($projectDB) { return new ArrayList(new Collection($projectDB, [Database::SYSTEM_COLLECTION_RULES], ['$collection' => Database::SYSTEM_COLLECTION_RULES, '$permissions' => ['read' => [], 'write' => []]])); }, 'Array of [rule objects](/docs/rules). Each rule define a collection field name, data type and validation.', false, ['projectDB']) + ->param('id', '', new Text(255), 'Collection ID. Max length: 255 chars.') ->inject('response') - ->inject('projectDB') + ->inject('dbForExternal') ->inject('audits') - ->action(function ($name, $read, $write, $rules, $response, $projectDB, $audits) { + ->action(function ($id, $response, $dbForExternal, $audits) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForExternal*/ /** @var Appwrite\Event\Event $audits */ - - $parsedRules = []; - - foreach ($rules as &$rule) { - $parsedRules[] = \array_merge([ - '$collection' => Database::SYSTEM_COLLECTION_RULES, - '$permissions' => [ - 'read' => $read, - 'write' => $write, - ], - ], $rule); - } - - try { - $data = $projectDB->createDocument([ - '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, - 'name' => $name, - 'dateCreated' => \time(), - 'dateUpdated' => \time(), - 'structure' => true, - '$permissions' => [ - 'read' => $read, - 'write' => $write, - ], - 'rules' => $parsedRules, - ]); - } catch (AuthorizationException $exception) { - throw new Exception('Unauthorized permissions', 401); - } catch (StructureException $exception) { - throw new Exception('Bad structure. '.$exception->getMessage(), 400); - } catch (\Exception $exception) { - throw new Exception('Failed saving document to DB', 500); - } - - if (false === $data) { - throw new Exception('Failed saving collection to DB', 500); - } + + $collection = $dbForExternal->createCollection($id); + $collection = $dbForExternal->getCollection($id); $audits ->setParam('event', 'database.collections.create') - ->setParam('resource', 'database/collection/'.$data->getId()) - ->setParam('data', $data->getArrayCopy()) + ->setParam('resource', 'database/collection/'.$collection->getId()) + ->setParam('data', $collection->getArrayCopy()) ; $response->setStatusCode(Response::STATUS_CODE_CREATED); - $response->dynamic($data, Response::MODEL_COLLECTION); + $response->dynamic2($collection, Response::MODEL_COLLECTION); }); App::get('/v1/database/collections') @@ -106,29 +69,19 @@ App::get('/v1/database/collections') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_COLLECTION_LIST) - ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) ->param('limit', 25, new Range(0, 100), 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) ->param('offset', 0, new Range(0, 40000), 'Results offset. The default value is 0. Use this param to manage pagination.', true) - ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true) ->inject('response') - ->inject('projectDB') - ->action(function ($search, $limit, $offset, $orderType, $response, $projectDB) { + ->inject('dbForExternal') + ->action(function ($limit, $offset, $response, $dbForExternal) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForExternal */ - $results = $projectDB->getCollection([ - 'limit' => $limit, - 'offset' => $offset, - 'orderType' => $orderType, - 'search' => $search, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_COLLECTIONS, - ], - ]); + $collections = $dbForExternal->listCollections($limit, $offset); - $response->dynamic(new Document([ - 'collections' => $results, - 'sum' => $projectDB->getSum(), + $response->dynamic2(new Document2([ + 'collections' => $collections, + 'sum' => \count($collections), ]), Response::MODEL_COLLECTION_LIST); }); @@ -146,17 +99,17 @@ App::get('/v1/database/collections/:collectionId') ->param('collectionId', '', new UID(), 'Collection unique ID.') ->inject('response') ->inject('projectDB') - ->action(function ($collectionId, $response, $projectDB) { + ->action(function ($collectionId, $response, $dbForExternal) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForExternal */ - $collection = $projectDB->getDocument($collectionId, false); + $collection = $dbForExternal->getCollection($collectionId); - if (empty($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) { + if (empty($collection)) { throw new Exception('Collection not found', 404); } - $response->dynamic($collection, Response::MODEL_COLLECTION); + $response->dynamic2($collection, Response::MODEL_COLLECTION); }); App::put('/v1/database/collections/:collectionId') @@ -249,33 +202,32 @@ App::delete('/v1/database/collections/:collectionId') ->label('sdk.response.model', Response::MODEL_NONE) ->param('collectionId', '', new UID(), 'Collection unique ID.') ->inject('response') - ->inject('projectDB') + ->inject('dbForExternal') ->inject('events') ->inject('audits') ->inject('deletes') - ->action(function ($collectionId, $response, $projectDB, $events, $audits, $deletes) { + ->action(function ($collectionId, $response, $dbForExternal, $events, $audits, $deletes) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForExternal */ /** @var Appwrite\Event\Event $events */ /** @var Appwrite\Event\Event $audits */ - $collection = $projectDB->getDocument($collectionId, false); + $collection = $dbForExternal->getCollection($collectionId); - if (empty($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) { + if (empty($collection)) { throw new Exception('Collection not found', 404); } - if (!$projectDB->deleteDocument($collectionId)) { - throw new Exception('Failed to remove collection from DB', 500); - } + $dbForExternal->deleteCollection($collectionId); - $deletes - ->setParam('type', DELETE_TYPE_DOCUMENT) - ->setParam('document', $collection) - ; + // TODO@kodumbeats use worker to handle this + // $deletes + // ->setParam('type', DELETE_TYPE_DOCUMENT) + // ->setParam('document', $collection) + // ; $events - ->setParam('eventData', $response->output($collection, Response::MODEL_COLLECTION)) + ->setParam('eventData', $response->output2($collection, Response::MODEL_COLLECTION)) ; $audits @@ -308,7 +260,7 @@ App::post('/v1/database/collections/:collectionId/attributes') ->param('signed', null, new Boolean(), 'Is attribute signed?') ->param('array', null, new Boolean(), 'Is attribute an array?') // TODO@kodumbeats "We should have here a whitelist of allowed values. We should add a link to a reference in the future documentation. We might be able to hide this option from the public API for now." - ->param('filters', [], new ArrayList(), 'Array of filters.') + ->param('filters', [], new ArrayList(new Text(256)), 'Array of filters.') ->inject('response') ->inject('dbForExternal') ->inject('audits') @@ -700,17 +652,17 @@ App::post('/v1/database/collections/:collectionId/documents') ->param('data', [], new JSON(), 'Document data as JSON object.') ->param('read', null, new ArrayList(new Text(64)), 'An array of strings with read permissions. By default only the current user is granted with read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true) ->param('write', null, new ArrayList(new Text(64)), 'An array of strings with write permissions. By default only the current user is granted with write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true) - ->param('parentDocument', '', new UID(), 'Parent document unique ID. Use when you want your new document to be a child of a parent document.', true) - ->param('parentProperty', '', new Key(), 'Parent document property name. Use when you want your new document to be a child of a parent document.', true) - ->param('parentPropertyType', Document::SET_TYPE_ASSIGN, new WhiteList([Document::SET_TYPE_ASSIGN, Document::SET_TYPE_APPEND, Document::SET_TYPE_PREPEND], true), 'Parent document property connection type. You can set this value to **assign**, **append** or **prepend**, default value is assign. Use when you want your new document to be a child of a parent document.', true) + // ->param('parentDocument', '', new UID(), 'Parent document unique ID. Use when you want your new document to be a child of a parent document.', true) + // ->param('parentProperty', '', new Key(), 'Parent document property name. Use when you want your new document to be a child of a parent document.', true) + // ->param('parentPropertyType', Document::SET_TYPE_ASSIGN, new WhiteList([Document::SET_TYPE_ASSIGN, Document::SET_TYPE_APPEND, Document::SET_TYPE_PREPEND], true), 'Parent document property connection type. You can set this value to **assign**, **append** or **prepend**, default value is assign. Use when you want your new document to be a child of a parent document.', true) ->inject('response') - ->inject('projectDB') + ->inject('dbForExternal') ->inject('user') ->inject('audits') - ->action(function ($collectionId, $data, $read, $write, $parentDocument, $parentProperty, $parentPropertyType, $response, $projectDB, $user, $audits) { + ->action(function ($collectionId, $data, $read, $write, /*$parentDocument, $parentProperty, $parentPropertyType,*/ $response, $dbForExternal, $user, $audits) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ - /** @var Appwrite\Database\Document $user */ + /** @var Utopia\Database\Database $dbForExternal */ + /** @var Utopia\Database\Document $user */ /** @var Appwrite\Event\Event $audits */ $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array @@ -723,85 +675,38 @@ App::post('/v1/database/collections/:collectionId/documents') throw new Exception('$id is not allowed for creating new documents, try update instead', 400); } - $collection = $projectDB->getDocument($collectionId, false); + $collection = $dbForExternal->getCollection($collectionId); - if (\is_null($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) { + if (empty($collection)) { throw new Exception('Collection not found', 404); } $data['$collection'] = $collectionId; // Adding this param to make API easier for developers - $data['$permissions'] = [ - 'read' => (is_null($read) && !$user->isEmpty()) ? ['user:'.$user->getId()] : $read ?? [], // By default set read permissions for user - 'write' => (is_null($write) && !$user->isEmpty()) ? ['user:'.$user->getId()] : $write ?? [], // By default set write permissions for user - ]; - - // Read parent document + validate not 404 + validate read / write permission like patch method - // Add payload to parent document property - if ((!empty($parentDocument)) && (!empty($parentProperty))) { - $parentDocument = $projectDB->getDocument($parentDocument, false); - - if (empty($parentDocument->getArrayCopy())) { // Check empty - throw new Exception('No parent document found', 404); - } - - /* - * 1. Check child has valid structure, - * 2. Check user have write permission for parent document - * 3. Assign parent data (including child) to $data - * 4. Validate the combined result has valid structure (inside $projectDB->createDocument method) - */ - - $new = new Document($data); - - $structure = new Structure($projectDB); - - if (!$structure->isValid($new)) { - throw new Exception('Invalid data structure: '.$structure->getDescription(), 400); - } - - $authorization = new Authorization($parentDocument, 'write'); - - if (!$authorization->isValid($new->getPermissions())) { - throw new Exception('Unauthorized permissions', 401); - } - - $parentDocument - ->setAttribute($parentProperty, $data, $parentPropertyType); - - $data = $parentDocument->getArrayCopy(); - $collection = $projectDB->getDocument($parentDocument->getCollection(), false); - } + $data['$read'] = (is_null($read) && !$user->isEmpty()) ? ['user:'.$user->getId()] : $read ?? []; // By default set read permissions for user + $data['$write'] = (is_null($write) && !$user->isEmpty()) ? ['user:'.$user->getId()] : $write ?? []; // By default set write permissions for user /** - * Set default collection values + * TODO@kodumbeats Set default collection values */ - foreach ($collection->getAttribute('rules') as $key => $rule) { - $key = $rule['key'] ?? ''; - $default = $rule['default'] ?? null; + // foreach ($collection->getAttribute('rules') as $key => $rule) { + // $key = $rule['key'] ?? ''; + // $default = $rule['default'] ?? null; - if (!isset($data[$key])) { - $data[$key] = $default; - } - } + // if (!isset($data[$key])) { + // $data[$key] = $default; + // } + // } - try { - $data = $projectDB->createDocument($data); - } catch (AuthorizationException $exception) { - throw new Exception('Unauthorized permissions', 401); - } catch (StructureException $exception) { - throw new Exception('Bad structure. '.$exception->getMessage(), 400); - } catch (\Exception $exception) { - throw new Exception('Failed saving document to DB'.$exception->getMessage(), 500); - } + $document = $dbForExternal->createDocument($collectionId, new Document2($data)); $audits ->setParam('event', 'database.documents.create') - ->setParam('resource', 'database/document/'.$data['$id']) - ->setParam('data', $data->getArrayCopy()) + ->setParam('resource', 'database/document/'.$document->getId()) + ->setParam('data', $document->getArrayCopy()) ; $response->setStatusCode(Response::STATUS_CODE_CREATED); - $response->dynamic($data, Response::MODEL_DOCUMENT); + $response->dynamic2($document, Response::MODEL_DOCUMENT); }); App::get('/v1/database/collections/:collectionId/documents') @@ -881,19 +786,24 @@ App::get('/v1/database/collections/:collectionId/documents/:documentId') ->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') ->param('documentId', null, new UID(), 'Document unique ID.') ->inject('response') - ->inject('projectDB') - ->action(function ($collectionId, $documentId, $response, $projectDB) { + ->inject('dbForExternal') + ->action(function ($collectionId, $documentId, $response, $dbForExternal) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForExternal */ - $document = $projectDB->getDocument($documentId, false); - $collection = $projectDB->getDocument($collectionId, false); + $collection = $dbForExternal->getCollection($collectionId); - if (empty($document->getArrayCopy()) || $document->getCollection() != $collection->getId()) { // Check empty + if (empty($collection)) { + throw new Exception('Collection not found', 404); + } + + $document = $dbForExternal->getDocument($collectionId, $documentId); + + if ($document->isEmpty()) { throw new Exception('No document found', 404); } - $response->dynamic($document, Response::MODEL_DOCUMENT); + $response->dynamic2($document, Response::MODEL_DOCUMENT); }); App::patch('/v1/database/collections/:collectionId/documents/:documentId') @@ -985,35 +895,28 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId') ->inject('projectDB') ->inject('events') ->inject('audits') - ->action(function ($collectionId, $documentId, $response, $projectDB, $events, $audits) { + ->action(function ($collectionId, $documentId, $response, $dbForExternal, $events, $audits) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForExternal */ /** @var Appwrite\Event\Event $events */ /** @var Appwrite\Event\Event $audits */ - $collection = $projectDB->getDocument($collectionId, false); - $document = $projectDB->getDocument($documentId, false); + $collection = $dbForExternal->getCollection($collectionId); - if (empty($document->getArrayCopy()) || $document->getCollection() != $collectionId) { // Check empty - throw new Exception('No document found', 404); - } - - if (\is_null($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) { + if (empty($collection)) { throw new Exception('Collection not found', 404); } - try { - $projectDB->deleteDocument($documentId); - } catch (AuthorizationException $exception) { - throw new Exception('Unauthorized permissions', 401); - } catch (StructureException $exception) { - throw new Exception('Bad structure. '.$exception->getMessage(), 400); - } catch (\Exception $exception) { - throw new Exception('Failed to remove document from DB', 500); + $document = $dbForExternal->getDocument($collectionId, $documentId); + + if ($document->isEmpty()) { + throw new Exception('No document found', 404); } + $success = $dbForExternal->deleteDocument($collectionId, $documentId); + $events - ->setParam('eventData', $response->output($document, Response::MODEL_DOCUMENT)) + ->setParam('eventData', $response->output2($document, Response::MODEL_DOCUMENT)) ; $audits From 4e068e63bfae8a31120fa517652b00f50e9e94cb Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 10 Jun 2021 15:38:54 -0400 Subject: [PATCH 12/90] Revert "Update utopia-php/database to 0.3" This reverts commit 2c7e65af683e0f987308c35fa298e17e3bdb619e. --- composer.json | 2 +- composer.lock | 20 +++++++++----------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/composer.json b/composer.json index 8e46eccb4..c5cf7a271 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": "^0.3.0", + "utopia-php/database": "0.2.*", "utopia-php/locale": "0.3.*", "utopia-php/registry": "0.4.*", "utopia-php/preloader": "0.2.*", diff --git a/composer.lock b/composer.lock index 6069475d8..b2d89a1e6 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": "854dfbc035c52bb373210e5d31271c65", + "content-hash": "5cf39daf305902a168233757d4a00066", "packages": [ { "name": "adhocore/jwt", @@ -1919,16 +1919,16 @@ }, { "name": "utopia-php/database", - "version": "0.3.0", + "version": "0.2.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "c5f9599d933f493ca69e57d34d903939425662f8" + "reference": "b5dd144d582f3355c13f5430b1b3d7eb850bc5cd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/c5f9599d933f493ca69e57d34d903939425662f8", - "reference": "c5f9599d933f493ca69e57d34d903939425662f8", + "url": "https://api.github.com/repos/utopia-php/database/zipball/b5dd144d582f3355c13f5430b1b3d7eb850bc5cd", + "reference": "b5dd144d582f3355c13f5430b1b3d7eb850bc5cd", "shasum": "" }, "require": { @@ -1941,9 +1941,7 @@ "utopia-php/framework": "0.*.*" }, "require-dev": { - "fakerphp/faker": "^1.14", "phpunit/phpunit": "^9.4", - "utopia-php/cli": "^0.11.0", "vimeo/psalm": "4.0.1" }, "type": "library", @@ -1976,9 +1974,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.3.0" + "source": "https://github.com/utopia-php/database/tree/0.2.0" }, - "time": "2021-06-09T12:39:37+00:00" + "time": "2021-05-26T18:41:44+00:00" }, { "name": "utopia-php/domains", @@ -6144,5 +6142,5 @@ "platform-overrides": { "php": "8.0" }, - "plugin-api-version": "2.1.0" -} + "plugin-api-version": "2.0.0" +} \ No newline at end of file From fbb54a1f6da10968feb411b5bc9f239884c0b10a Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 10 Jun 2021 16:41:33 -0400 Subject: [PATCH 13/90] Add attribute and index scopes --- app/config/roles.php | 8 ++++++++ app/config/scopes.php | 12 ++++++++++++ tests/e2e/Scopes/ProjectCustom.php | 4 ++++ 3 files changed, 24 insertions(+) diff --git a/app/config/roles.php b/app/config/roles.php index 3e06ddbfd..1d1e761f0 100644 --- a/app/config/roles.php +++ b/app/config/roles.php @@ -11,6 +11,10 @@ $member = [ 'teams.write', 'documents.read', 'documents.write', + 'attributes.read', + 'attributes.write', + 'indexes.read', + 'indexes.write', 'files.read', 'files.write', 'projects.read', @@ -32,6 +36,10 @@ $admins = [ 'users.write', 'collections.read', 'collections.write', + 'attributes.read', + 'attributes.write', + 'indexes.read', + 'indexes.write', 'platforms.read', 'platforms.write', 'keys.read', diff --git a/app/config/scopes.php b/app/config/scopes.php index 088502c5d..cf40fb076 100644 --- a/app/config/scopes.php +++ b/app/config/scopes.php @@ -19,6 +19,18 @@ return [ // List of publicly visible scopes 'collections.write' => [ 'description' => 'Access to create, update, and delete your project\'s database collections', ], + 'attributes.read' => [ + 'description' => 'Access to read your project\'s database collection\'s attributes', + ], + 'attributes.write' => [ + 'description' => 'Access to create, update, and delete your project\'s database collection\'s attributes', + ], + 'indexes.read' => [ + 'description' => 'Access to read your project\'s database collection\'s indexes', + ], + 'indexes.write' => [ + 'description' => 'Access to create, update, and delete your project\'s database collection\'s indexes', + ], 'documents.read' => [ 'description' => 'Access to read your project\'s database documents', ], diff --git a/tests/e2e/Scopes/ProjectCustom.php b/tests/e2e/Scopes/ProjectCustom.php index 3f8028528..431762b6a 100644 --- a/tests/e2e/Scopes/ProjectCustom.php +++ b/tests/e2e/Scopes/ProjectCustom.php @@ -69,6 +69,10 @@ trait ProjectCustom 'teams.write', 'collections.read', 'collections.write', + 'attributes.read', + 'attributes.write', + 'indexes.read', + 'indexes.write', 'documents.read', 'documents.write', 'files.read', From 3510bc88b476d9cba1d112bde4bb9f868d2e8c59 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 11 Jun 2021 10:25:52 -0400 Subject: [PATCH 14/90] use isEmpty document method to check for empty collection --- app/controllers/api/database.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index f2949322e..864a06dab 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -105,7 +105,7 @@ App::get('/v1/database/collections/:collectionId') $collection = $dbForExternal->getCollection($collectionId); - if (empty($collection)) { + if ($collection->isEmpty()) { throw new Exception('Collection not found', 404); } @@ -214,7 +214,7 @@ App::delete('/v1/database/collections/:collectionId') $collection = $dbForExternal->getCollection($collectionId); - if (empty($collection)) { + if ($collection->isEmpty()) { throw new Exception('Collection not found', 404); } @@ -271,7 +271,7 @@ App::post('/v1/database/collections/:collectionId/attributes') $collection = $dbForExternal->getCollection($collectionId); - if (empty($collection)) { + if ($collection->isEmpty()) { throw new Exception('Collection not found', 404); } @@ -321,7 +321,7 @@ App::get('v1/database/collections/:collectionId/attributes') $collection = $dbForExternal->getCollection($collectionId); - if (empty($collection)) { + if ($collection->isEmpty()) { throw new Exception('Collection not found', 404); } @@ -401,7 +401,7 @@ App::delete('/v1/database/collections/:collectionId/attributes/:attributeId') $collection = $dbForExternal->getCollection($collectionId); - if (empty($collection)) { + if ($collection->isEmpty()) { throw new Exception('Collection not found', 404); } @@ -468,7 +468,7 @@ App::post('/v1/database/collections/:collectionId/indexes') $collection = $dbForExternal->getCollection($collectionId); - if (empty($collection)) { + if ($collection->isEmpty()) { throw new Exception('Collection not found', 404); } @@ -516,7 +516,7 @@ App::get('v1/database/collections/:collectionId/indexes') $collection = $dbForExternal->getCollection($collectionId); - if (empty($collection)) { + if ($collection->isEmpty()) { throw new Exception('Collection not found', 404); } @@ -546,11 +546,11 @@ App::get('v1/database/collections/:collectionId/indexes/:indexId') ->inject('projectDB') ->action(function ($collectionId, $indexId, $response, $dbForExternal) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForExternal */ $collection = $dbForExternal->getCollection($collectionId); - if (empty($collection)) { + if ($collection->isEmpty()) { throw new Exception('Collection not found', 404); } From 8aecf3cf6ae22d18790cc6922e062d2491123001 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 11 Jun 2021 11:27:51 -0400 Subject: [PATCH 15/90] Use response models for attirbute types --- .../Utopia/Response/Model/Collection.php | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/Appwrite/Utopia/Response/Model/Collection.php b/src/Appwrite/Utopia/Response/Model/Collection.php index 725267c0a..a3659e027 100644 --- a/src/Appwrite/Utopia/Response/Model/Collection.php +++ b/src/Appwrite/Utopia/Response/Model/Collection.php @@ -4,6 +4,7 @@ namespace Appwrite\Utopia\Response\Model; use Appwrite\Utopia\Response; use Appwrite\Utopia\Response\Model; +use stdClass; class Collection extends Model { @@ -37,31 +38,31 @@ class Collection extends Model 'example' => '', ]) ->addRule('attributes', [ - 'type' => self::TYPE_STRING, + 'type' => Response::MODEL_ATTRIBUTE, 'description' => 'Collection attributes.', - 'default' => '', - 'example' => '', + 'default' => [], + 'example' => new stdClass, 'array' => true ]) ->addRule('indexes', [ - 'type' => self::TYPE_STRING, + 'type' => Response::MODEL_INDEX, 'description' => 'Collection indexes.', - 'default' => '', - 'example' => '', + 'default' => [], + 'example' => new stdClass, 'array' => true ]) ->addRule('attributesInQueue', [ - 'type' => self::TYPE_STRING, + 'type' => Response::MODEL_ATTRIBUTE, 'description' => 'Collection attributes in creation queue.', - 'default' => '', - 'example' => '', + 'default' => [], + 'example' => new stdClass, 'array' => true ]) ->addRule('indexesInQueue', [ - 'type' => self::TYPE_STRING, + 'type' => Response::MODEL_INDEX, 'description' => 'Collection indexes in creation queue.', - 'default' => '', - 'example' => '', + 'default' => [], + 'example' => new stdClass, 'array' => true ]) ; From 1885a9632d41fa4d1d48644f064b32802e4313a5 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 11 Jun 2021 14:07:05 -0400 Subject: [PATCH 16/90] Fix tests for create/delete collection --- app/controllers/api/database.php | 20 ++--- src/Appwrite/Utopia/Response.php | 2 + .../Database/DatabaseCustomServerTest.php | 75 ++++++++++--------- 3 files changed, 51 insertions(+), 46 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 864a06dab..abc935803 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -24,6 +24,8 @@ use Utopia\Database\Document as Document2; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization as Authorization2; +use function Amp\Promise\wait; + App::post('/v1/database/collections') ->desc('Create Collection') ->groups(['api', 'database']) @@ -98,11 +100,11 @@ App::get('/v1/database/collections/:collectionId') ->label('sdk.response.model', Response::MODEL_COLLECTION) ->param('collectionId', '', new UID(), 'Collection unique ID.') ->inject('response') - ->inject('projectDB') + ->inject('dbForExternal') ->action(function ($collectionId, $response, $dbForExternal) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForExternal */ - + $collection = $dbForExternal->getCollection($collectionId); if ($collection->isEmpty()) { @@ -257,10 +259,10 @@ App::post('/v1/database/collections/:collectionId/attributes') // TODO@kodumbeats add units to description ->param('size', null, new Numeric(), 'Attribute size.') ->param('required', null, new Boolean(), 'Is attribute required?') - ->param('signed', null, new Boolean(), 'Is attribute signed?') - ->param('array', null, new Boolean(), 'Is attribute an array?') + ->param('signed', true, new Boolean(), 'Is attribute signed?', true) + ->param('array', false, new Boolean(), 'Is attribute an array?', true) // TODO@kodumbeats "We should have here a whitelist of allowed values. We should add a link to a reference in the future documentation. We might be able to hide this option from the public API for now." - ->param('filters', [], new ArrayList(new Text(256)), 'Array of filters.') + ->param('filters', [], new ArrayList(new Text(256)), 'Array of filters.', true) ->inject('response') ->inject('dbForExternal') ->inject('audits') @@ -597,7 +599,7 @@ App::delete('/v1/database/collections/:collectionId/indexes/:indexId') $collection = $dbForExternal->getCollection($collectionId); - if (empty($collection)) { + if ($collection->isEmpty()) { throw new Exception('Collection not found', 404); } @@ -677,7 +679,7 @@ App::post('/v1/database/collections/:collectionId/documents') $collection = $dbForExternal->getCollection($collectionId); - if (empty($collection)) { + if ($collection->isEmpty()) { throw new Exception('Collection not found', 404); } @@ -793,7 +795,7 @@ App::get('/v1/database/collections/:collectionId/documents/:documentId') $collection = $dbForExternal->getCollection($collectionId); - if (empty($collection)) { + if ($collection->isEmpty()) { throw new Exception('Collection not found', 404); } @@ -903,7 +905,7 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId') $collection = $dbForExternal->getCollection($collectionId); - if (empty($collection)) { + if ($collection->isEmpty()) { throw new Exception('Collection not found', 404); } diff --git a/src/Appwrite/Utopia/Response.php b/src/Appwrite/Utopia/Response.php index 65ec5230a..bbae1c3e9 100644 --- a/src/Appwrite/Utopia/Response.php +++ b/src/Appwrite/Utopia/Response.php @@ -182,6 +182,8 @@ class Response extends SwooleResponse // Entities ->setModel(new Permissions()) ->setModel(new Collection()) + ->setModel(new Attribute()) + ->setModel(new Index()) ->setModel(new ModelDocument()) ->setModel(new Rule()) ->setModel(new Log()) diff --git a/tests/e2e/Services/Database/DatabaseCustomServerTest.php b/tests/e2e/Services/Database/DatabaseCustomServerTest.php index 559072875..607b31ba1 100644 --- a/tests/e2e/Services/Database/DatabaseCustomServerTest.php +++ b/tests/e2e/Services/Database/DatabaseCustomServerTest.php @@ -25,36 +25,39 @@ class DatabaseCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'name' => 'Actors', - 'read' => ['*'], - 'write' => ['role:member', 'role:admin'], - 'rules' => [ - [ - 'label' => 'First Name', - 'key' => 'firstName', - 'type' => 'text', - 'default' => '', - 'required' => true, - 'array' => false - ], - [ - 'label' => 'Last Name', - 'key' => 'lastName', - 'type' => 'text', - 'default' => '', - 'required' => true, - 'array' => false - ], - ], + 'id' => 'Actors', ]); $this->assertEquals($actors['headers']['status-code'], 201); $this->assertEquals($actors['body']['name'], 'Actors'); - $this->assertIsArray($actors['body']['$permissions']); - $this->assertIsArray($actors['body']['$permissions']['read']); - $this->assertIsArray($actors['body']['$permissions']['write']); - $this->assertCount(1, $actors['body']['$permissions']['read']); - $this->assertCount(2, $actors['body']['$permissions']['write']); + + $firstName = $this->client->call(Client::METHOD_POST, '/database/collections/' . $actors['body']['$id'] . '/attributes', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'id' => 'firstName', + 'type' => 'string', + 'size' => 256, + 'required' => true, + ]); + + $lastName = $this->client->call(Client::METHOD_POST, '/database/collections/' . $actors['body']['$id'] . '/attributes', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'id' => 'lastName', + 'type' => 'string', + 'size' => 256, + 'required' => true, + ]); + + $collection = $this->client->call(Client::METHOD_GET, '/database/collections/' . $actors['body']['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), []); // Add Documents to the collection $document1 = $this->client->call(Client::METHOD_POST, '/database/collections/' . $actors['body']['$id'] . '/documents', array_merge([ @@ -80,24 +83,22 @@ class DatabaseCustomServerTest extends Scope 'read' => ['user:'.$this->getUser()['$id']], 'write' => ['user:'.$this->getUser()['$id']], ]); - + $this->assertEquals($document1['headers']['status-code'], 201); $this->assertEquals($document1['body']['$collection'], $actors['body']['$id']); - $this->assertIsArray($document1['body']['$permissions']); - $this->assertIsArray($document1['body']['$permissions']['read']); - $this->assertIsArray($document1['body']['$permissions']['write']); - $this->assertCount(1, $document1['body']['$permissions']['read']); - $this->assertCount(1, $document1['body']['$permissions']['write']); + $this->assertIsArray($document1['body']['$read']); + $this->assertIsArray($document1['body']['$write']); + $this->assertCount(1, $document1['body']['$read']); + $this->assertCount(1, $document1['body']['$write']); $this->assertEquals($document1['body']['firstName'], 'Tom'); $this->assertEquals($document1['body']['lastName'], 'Holland'); $this->assertEquals($document2['headers']['status-code'], 201); $this->assertEquals($document2['body']['$collection'], $actors['body']['$id']); - $this->assertIsArray($document2['body']['$permissions']); - $this->assertIsArray($document2['body']['$permissions']['read']); - $this->assertIsArray($document2['body']['$permissions']['write']); - $this->assertCount(1, $document2['body']['$permissions']['read']); - $this->assertCount(1, $document2['body']['$permissions']['write']); + $this->assertIsArray($document2['body']['$read']); + $this->assertIsArray($document2['body']['$write']); + $this->assertCount(1, $document2['body']['$read']); + $this->assertCount(1, $document2['body']['$write']); $this->assertEquals($document2['body']['firstName'], 'Samuel'); $this->assertEquals($document2['body']['lastName'], 'Jackson'); From b8950930d7a1df2322bf8cfc244180804166e7b5 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 11 Jun 2021 14:47:23 -0400 Subject: [PATCH 17/90] Fix missing bracket --- tests/e2e/Services/Database/DatabaseCustomServerTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/e2e/Services/Database/DatabaseCustomServerTest.php b/tests/e2e/Services/Database/DatabaseCustomServerTest.php index 8cd7a248b..607b31ba1 100644 --- a/tests/e2e/Services/Database/DatabaseCustomServerTest.php +++ b/tests/e2e/Services/Database/DatabaseCustomServerTest.php @@ -120,3 +120,4 @@ class DatabaseCustomServerTest extends Scope $this->assertEquals($response['headers']['status-code'], 404); } +} \ No newline at end of file From e35d883c56488ba1ea38fc6bc4f8256670b0e92c Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 11 Jun 2021 16:06:54 -0400 Subject: [PATCH 18/90] Test for creating collections and documents --- app/controllers/api/database.php | 9 +- tests/e2e/Services/Database/DatabaseBase.php | 225 +++++++------------ 2 files changed, 87 insertions(+), 147 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index abc935803..ff9d1b7b7 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -17,7 +17,7 @@ use Appwrite\Database\Validator\Structure; use Appwrite\Database\Validator\Collection; use Appwrite\Database\Validator\Authorization; use Appwrite\Database\Exception\Authorization as AuthorizationException; -use Appwrite\Database\Exception\Structure as StructureException; +use Utopia\Database\Exception\Structure as StructureException; use Appwrite\Utopia\Response; use Utopia\Database\Database as Database2; use Utopia\Database\Document as Document2; @@ -699,7 +699,12 @@ App::post('/v1/database/collections/:collectionId/documents') // } // } - $document = $dbForExternal->createDocument($collectionId, new Document2($data)); + // TODO@kodumbeats catch other exceptions + try { + $document = $dbForExternal->createDocument($collectionId, new Document2($data)); + } catch (StructureException $exception) { + throw new Exception($exception->getMessage(), 400); + } $audits ->setParam('event', 'database.documents.create') diff --git a/tests/e2e/Services/Database/DatabaseBase.php b/tests/e2e/Services/Database/DatabaseBase.php index d018f29a8..72deef0af 100644 --- a/tests/e2e/Services/Database/DatabaseBase.php +++ b/tests/e2e/Services/Database/DatabaseBase.php @@ -11,93 +11,69 @@ trait DatabaseBase /** * Test for SUCCESS */ - $actors = $this->client->call(Client::METHOD_POST, '/database/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'name' => 'Actors', - 'read' => ['role:all'], - 'write' => ['role:member', 'role:admin'], - 'rules' => [ - [ - 'label' => 'First Name', - 'key' => 'firstName', - 'type' => 'text', - 'default' => '', - 'required' => true, - 'array' => false - ], - [ - 'label' => 'Last Name', - 'key' => 'lastName', - 'type' => 'text', - 'default' => '', - 'required' => true, - 'array' => false - ], - ], - ]); - - $this->assertEquals($actors['headers']['status-code'], 201); - $this->assertEquals($actors['body']['name'], 'Actors'); - $this->assertIsArray($actors['body']['$permissions']); - $this->assertIsArray($actors['body']['$permissions']['read']); - $this->assertIsArray($actors['body']['$permissions']['write']); - $this->assertCount(1, $actors['body']['$permissions']['read']); - $this->assertCount(2, $actors['body']['$permissions']['write']); - $movies = $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'] ]), [ - 'name' => 'Movies', - 'read' => ['role:all'], - 'write' => ['role:member', 'role:admin'], - 'rules' => [ - [ - 'label' => 'Name', - 'key' => 'name', - 'type' => 'text', - 'default' => '', - 'required' => true, - 'array' => false - ], - [ - 'label' => 'Release Year', - 'key' => 'releaseYear', - 'type' => 'numeric', - 'default' => 0, - 'required' => false, - 'array' => false - ], - [ - 'label' => 'Actors', - 'key' => 'actors', - 'type' => 'document', - 'default' => [], - 'required' => false, - 'array' => true, - 'list' => [$actors['body']['$id']], - ], - ], + 'id' => 'Movies', ]); $this->assertEquals($movies['headers']['status-code'], 201); $this->assertEquals($movies['body']['name'], 'Movies'); - $this->assertIsArray($movies['body']['$permissions']); - $this->assertIsArray($movies['body']['$permissions']['read']); - $this->assertIsArray($movies['body']['$permissions']['write']); - $this->assertCount(1, $movies['body']['$permissions']['read']); - $this->assertCount(2, $movies['body']['$permissions']['write']); - return array_merge(['moviesId' => $movies['body']['$id'], 'actorsId' => $actors['body']['$id']]); + return ['moviesId' => $movies['body']['$id']]; } /** * @depends testCreateCollection */ + public function testCreateAttributes(array $data): array + { + $title = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['moviesId'] . '/attributes', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'id' => 'title', + 'type' => 'string', + 'size' => 256, + 'required' => true, + ]); + + $releaseYear = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['moviesId'] . '/attributes', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'id' => 'releaseYear', + 'type' => 'integer', + 'size' => 0, + 'required' => true, + ]); + + $actors = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['moviesId'] . '/attributes', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'id' => 'actors', + 'type' => 'string', + 'size' => 256, + 'required' => false, + 'array' => true, + ]); + + $this->assertEquals(1,1); + + return $data; + } + + // TODO@kodumbeats create and test indexes + + /** + * @depends testCreateAttributes + */ public function testCreateDocument(array $data):array { $document1 = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['moviesId'] . '/documents', array_merge([ @@ -105,21 +81,11 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'data' => [ - 'name' => 'Captain America', + 'title' => 'Captain America', 'releaseYear' => 1944, 'actors' => [ - [ - '$collection' => $data['actorsId'], - '$permissions' => ['read' => [], 'write' => []], - 'firstName' => 'Chris', - 'lastName' => 'Evans', - ], - [ - '$collection' => $data['actorsId'], - '$permissions' => ['read' => [], 'write' => []], - 'firstName' => 'Samuel', - 'lastName' => 'Jackson', - ], + 'Chris Evans', + 'Samuel Jackson', ] ], 'read' => ['user:'.$this->getUser()['$id']], @@ -131,27 +97,12 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'data' => [ - 'name' => 'Spider-Man: Far From Home', + 'title' => 'Spider-Man: Far From Home', 'releaseYear' => 2019, 'actors' => [ - [ - '$collection' => $data['actorsId'], - '$permissions' => ['read' => [], 'write' => []], - 'firstName' => 'Tom', - 'lastName' => 'Holland', - ], - [ - '$collection' => $data['actorsId'], - '$permissions' => ['read' => [], 'write' => []], - 'firstName' => 'Zendaya', - 'lastName' => 'Maree Stoermer', - ], - [ - '$collection' => $data['actorsId'], - '$permissions' => ['read' => [], 'write' => []], - 'firstName' => 'Samuel', - 'lastName' => 'Jackson', - ], + 'Tom Holland', + 'Zendaya Maree Stoermer', + 'Samuel Jackson', ] ], 'read' => ['user:'.$this->getUser()['$id']], @@ -163,21 +114,11 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'data' => [ - 'name' => 'Spider-Man: Homecoming', + 'title' => 'Spider-Man: Homecoming', 'releaseYear' => 2017, 'actors' => [ - [ - '$collection' => $data['actorsId'], - '$permissions' => ['read' => [], 'write' => []], - 'firstName' => 'Tom', - 'lastName' => 'Holland', - ], - [ - '$collection' => $data['actorsId'], - '$permissions' => ['read' => [], 'write' => []], - 'firstName' => 'Zendaya', - 'lastName' => 'Maree Stoermer', - ], + 'Tom Holland', + 'Zendaya Maree Stoermer', ], ], 'read' => ['user:'.$this->getUser()['$id']], @@ -197,46 +138,40 @@ trait DatabaseBase $this->assertEquals($document1['headers']['status-code'], 201); $this->assertEquals($document1['body']['$collection'], $data['moviesId']); - $this->assertEquals($document1['body']['name'], 'Captain America'); + $this->assertEquals($document1['body']['title'], 'Captain America'); $this->assertEquals($document1['body']['releaseYear'], 1944); - $this->assertIsArray($document1['body']['$permissions']); - $this->assertIsArray($document1['body']['$permissions']['read']); - $this->assertIsArray($document1['body']['$permissions']['write']); - $this->assertCount(1, $document1['body']['$permissions']['read']); - $this->assertCount(1, $document1['body']['$permissions']['write']); + $this->assertIsArray($document1['body']['$read']); + $this->assertIsArray($document1['body']['$write']); + $this->assertCount(1, $document1['body']['$read']); + $this->assertCount(1, $document1['body']['$write']); $this->assertCount(2, $document1['body']['actors']); + $this->assertEquals($document1['body']['actors'][0], 'Chris Evans'); + $this->assertEquals($document1['body']['actors'][1], 'Samuel Jackson'); $this->assertEquals($document2['headers']['status-code'], 201); $this->assertEquals($document2['body']['$collection'], $data['moviesId']); - $this->assertEquals($document2['body']['name'], 'Spider-Man: Far From Home'); + $this->assertEquals($document2['body']['title'], 'Spider-Man: Far From Home'); $this->assertEquals($document2['body']['releaseYear'], 2019); - $this->assertIsArray($document2['body']['$permissions']); - $this->assertIsArray($document2['body']['$permissions']['read']); - $this->assertIsArray($document2['body']['$permissions']['write']); - $this->assertCount(1, $document2['body']['$permissions']['read']); - $this->assertCount(1, $document2['body']['$permissions']['write']); + $this->assertIsArray($document2['body']['$read']); + $this->assertIsArray($document2['body']['$write']); + $this->assertCount(1, $document2['body']['$read']); + $this->assertCount(1, $document2['body']['$write']); $this->assertCount(3, $document2['body']['actors']); - $this->assertEquals($document2['body']['actors'][0]['firstName'], 'Tom'); - $this->assertEquals($document2['body']['actors'][0]['lastName'], 'Holland'); - $this->assertEquals($document2['body']['actors'][1]['firstName'], 'Zendaya'); - $this->assertEquals($document2['body']['actors'][1]['lastName'], 'Maree Stoermer'); - $this->assertEquals($document2['body']['actors'][2]['firstName'], 'Samuel'); - $this->assertEquals($document2['body']['actors'][2]['lastName'], 'Jackson'); + $this->assertEquals($document2['body']['actors'][0], 'Tom Holland'); + $this->assertEquals($document2['body']['actors'][1], 'Zendaya Maree Stoermer'); + $this->assertEquals($document2['body']['actors'][2], 'Samuel Jackson'); $this->assertEquals($document3['headers']['status-code'], 201); $this->assertEquals($document3['body']['$collection'], $data['moviesId']); - $this->assertEquals($document3['body']['name'], 'Spider-Man: Homecoming'); + $this->assertEquals($document3['body']['title'], 'Spider-Man: Homecoming'); $this->assertEquals($document3['body']['releaseYear'], 2017); - $this->assertIsArray($document3['body']['$permissions']); - $this->assertIsArray($document3['body']['$permissions']['read']); - $this->assertIsArray($document3['body']['$permissions']['write']); - $this->assertCount(1, $document3['body']['$permissions']['read']); - $this->assertCount(1, $document3['body']['$permissions']['write']); + $this->assertIsArray($document3['body']['$read']); + $this->assertIsArray($document3['body']['$write']); + $this->assertCount(1, $document3['body']['$read']); + $this->assertCount(1, $document3['body']['$write']); $this->assertCount(2, $document3['body']['actors']); - $this->assertEquals($document3['body']['actors'][0]['firstName'], 'Tom'); - $this->assertEquals($document3['body']['actors'][0]['lastName'], 'Holland'); - $this->assertEquals($document3['body']['actors'][1]['firstName'], 'Zendaya'); - $this->assertEquals($document3['body']['actors'][1]['lastName'], 'Maree Stoermer'); + $this->assertEquals($document2['body']['actors'][0], 'Tom Holland'); + $this->assertEquals($document2['body']['actors'][1], 'Zendaya Maree Stoermer'); $this->assertEquals($document4['headers']['status-code'], 400); From f7d3b726fdd59d764d32a427af52ee83c84178b4 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 11 Jun 2021 16:21:54 -0400 Subject: [PATCH 19/90] Remove erroneous use --- app/controllers/api/database.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index ff9d1b7b7..fd74ff5a0 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -24,8 +24,6 @@ use Utopia\Database\Document as Document2; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization as Authorization2; -use function Amp\Promise\wait; - App::post('/v1/database/collections') ->desc('Create Collection') ->groups(['api', 'database']) From 1d2d869fa857cc04ea968d018eda923a1e1ca63c Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 14 Jun 2021 15:54:19 -0400 Subject: [PATCH 20/90] Add assertions for creating attributes --- tests/e2e/Services/Database/DatabaseBase.php | 22 +++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/tests/e2e/Services/Database/DatabaseBase.php b/tests/e2e/Services/Database/DatabaseBase.php index 72deef0af..f7640fcff 100644 --- a/tests/e2e/Services/Database/DatabaseBase.php +++ b/tests/e2e/Services/Database/DatabaseBase.php @@ -64,7 +64,27 @@ trait DatabaseBase 'array' => true, ]); - $this->assertEquals(1,1); + $this->assertEquals($title['headers']['status-code'], 201); + $this->assertEquals($title['body']['$collection'], $data['moviesId']); + $this->assertEquals($title['body']['$id'], 'title'); + $this->assertEquals($title['body']['type'], 'string'); + $this->assertEquals($title['body']['size'], 256); + $this->assertEquals($title['body']['required'], true); + + $this->assertEquals($releaseYear['headers']['status-code'], 201); + $this->assertEquals($releaseYear['body']['$collection'], $data['moviesId']); + $this->assertEquals($releaseYear['body']['$id'], '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); + $this->assertEquals($actors['body']['$collection'], $data['moviesId']); + $this->assertEquals($actors['body']['$id'], 'actors'); + $this->assertEquals($actors['body']['type'], 'string'); + $this->assertEquals($actors['body']['size'], 256); + $this->assertEquals($actors['body']['required'], false); + $this->assertEquals($actors['body']['array'], true); return $data; } From 80f41edf36ef6fbdee75b74c54b72fcf7ec95096 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 14 Jun 2021 15:54:49 -0400 Subject: [PATCH 21/90] Test for creating indexes --- tests/e2e/Services/Database/DatabaseBase.php | 27 ++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/tests/e2e/Services/Database/DatabaseBase.php b/tests/e2e/Services/Database/DatabaseBase.php index f7640fcff..855cdf29f 100644 --- a/tests/e2e/Services/Database/DatabaseBase.php +++ b/tests/e2e/Services/Database/DatabaseBase.php @@ -89,11 +89,34 @@ trait DatabaseBase return $data; } - // TODO@kodumbeats create and test indexes - /** * @depends testCreateAttributes */ + public function testCreateIndexes(array $data): array + { + $titleIndex = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['moviesId'] . '/indexes', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'id' => 'titleIndex', + 'type' => 'fulltext', + 'attributes' => ['title'], + ]); + + $this->assertEquals($titleIndex['headers']['status-code'], 201); + $this->assertEquals($titleIndex['body']['$collection'], $data['moviesId']); + $this->assertEquals($titleIndex['body']['$id'], 'titleIndex'); + $this->assertEquals($titleIndex['body']['type'], 'fulltext'); + $this->assertCount(1, $titleIndex['body']['attributes']); + $this->assertEquals($titleIndex['body']['attributes'][0], 'title'); + + return $data; + } + + /** + * @depends testCreateIndexes + */ public function testCreateDocument(array $data):array { $document1 = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['moviesId'] . '/documents', array_merge([ From 33d07c2ba4109ab7517fe9b51a2537c9ba23b2d9 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 14 Jun 2021 15:55:36 -0400 Subject: [PATCH 22/90] Move listDocuments to new db --- app/controllers/api/database.php | 64 +++++-------- tests/e2e/Services/Database/DatabaseBase.php | 99 ++++++++++---------- 2 files changed, 71 insertions(+), 92 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index fd74ff5a0..b4467cba1 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -456,8 +456,8 @@ App::post('/v1/database/collections/:collectionId/indexes') // TODO@kodumbeats type should be a whitelist of supported index types ->param('type', null, new Text(256), 'Index type.') ->param('attributes', null, new ArrayList(new Text(256)), 'Array of attributes to index.') - ->param('lengths', null, new ArrayList(new Text(256)), 'Array of index lengths.') - ->param('orders', null, new ArrayList(new Text(256)), 'Array of index orders.') + ->param('lengths', [], new ArrayList(new Text(256)), 'Array of index lengths.', true) + ->param('orders', [], new ArrayList(new Text(256)), 'Array of index orders.', true) ->inject('response') ->inject('dbForExternal') ->inject('audits') @@ -725,56 +725,38 @@ App::get('/v1/database/collections/:collectionId/documents') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_DOCUMENT_LIST) - ->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') - ->param('filters', [], new ArrayList(new Text(128)), 'Array of filter strings. Each filter is constructed from a key name, comparison operator (=, !=, >, <, <=, >=) and a value. You can also use a dot (.) separator in attribute names to filter by child document attributes. Examples: \'name=John Doe\' or \'category.$id>=5bed2d152c362\'.', true) + ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') + ->param('queries', [], new ArrayList(new Text(128)), 'Array of query strings.', true) + // ->param('queries', [], new Text(128), 'Array of query strings.', true) ->param('limit', 25, new Range(0, 100), 'Maximum number of documents to return in response. Use this value to manage pagination. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) ->param('offset', 0, new Range(0, 900000000), 'Offset value. The default value is 0. Use this param to manage pagination.', true) - ->param('orderField', '', new Text(128), 'Document field that results will be sorted by.', true) - ->param('orderType', 'ASC', new WhiteList(['DESC', 'ASC'], true), 'Order direction. Possible values are DESC for descending order, or ASC for ascending order.', true) - ->param('orderCast', 'string', new WhiteList(['int', 'string', 'date', 'time', 'datetime'], true), 'Order field type casting. Possible values are int, string, date, time or datetime. The database will attempt to cast the order field to the value you pass here. The default value is a string.', true) - ->param('search', '', new Text(256), 'Search query. Enter any free text search. The database will try to find a match against all document attributes and children. Max length: 256 chars.', true) + ->param('orderAttributes', [], new ArrayList(new Text(128)), 'Array of attributes used to sort results.', true) + ->param('orderTypes', [], new ArrayList(new WhiteList(['DESC', 'ASC'], true)), 'Array of order directions for sorting attribtues. Possible values are DESC for descending order, or ASC for ascending order.', true) + // ->param('orderField', '', new Text(128), 'Document field that results will be sorted by.', true) + // ->param('orderCast', 'string', new WhiteList(['int', 'string', 'date', 'time', 'datetime'], true), 'Order field type casting. Possible values are int, string, date, time or datetime. The database will attempt to cast the order field to the value you pass here. The default value is a string.', true) + // ->param('search', '', new Text(256), 'Search query. Enter any free text search. The database will try to find a match against all document attributes and children. Max length: 256 chars.', true) ->inject('response') - ->inject('projectDB') - ->action(function ($collectionId, $filters, $limit, $offset, $orderField, $orderType, $orderCast, $search, $response, $projectDB) { + ->inject('dbForExternal') + ->action(function ($collectionId, $queries, $limit, $offset, $orderAttributes, $orderTypes, $response, $dbForExternal) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForExternal */ - $collection = $projectDB->getDocument($collectionId, false); + $collection = $dbForExternal->getCollection($collectionId); - if (\is_null($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) { + if ($collection->isEmpty()) { throw new Exception('Collection not found', 404); } - $list = $projectDB->getCollection([ - 'limit' => $limit, - 'offset' => $offset, - 'orderField' => $orderField, - 'orderType' => $orderType, - 'orderCast' => $orderCast, - 'search' => $search, - 'filters' => \array_merge($filters, [ - '$collection='.$collectionId, - ]), - ]); + $queries = \array_map(function ($query) { + return Query::parse($query); + }, $queries); - // if (App::isDevelopment()) { - // $collection - // ->setAttribute('debug', $projectDB->getDebug()) - // ->setAttribute('limit', $limit) - // ->setAttribute('offset', $offset) - // ->setAttribute('orderField', $orderField) - // ->setAttribute('orderType', $orderType) - // ->setAttribute('orderCast', $orderCast) - // ->setAttribute('filters', $filters) - // ; - // } + $documents = $dbForExternal->find($collectionId, $queries, $limit, $offset, $orderAttributes, $orderTypes); - $collection - ->setAttribute('sum', $projectDB->getSum()) - ->setAttribute('documents', $list) - ; - - $response->dynamic($collection, Response::MODEL_DOCUMENT_LIST); + $response->dynamic2(new Document2([ + 'sum' => \count($documents), + 'documents' => $documents, + ]), Response::MODEL_DOCUMENT_LIST); }); App::get('/v1/database/collections/:collectionId/documents/:documentId') diff --git a/tests/e2e/Services/Database/DatabaseBase.php b/tests/e2e/Services/Database/DatabaseBase.php index 855cdf29f..c51e0f352 100644 --- a/tests/e2e/Services/Database/DatabaseBase.php +++ b/tests/e2e/Services/Database/DatabaseBase.php @@ -230,9 +230,8 @@ trait DatabaseBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'orderField' => 'releaseYear', - 'orderType' => 'ASC', - 'orderCast' => 'int', + 'orderAttributes' => ['releaseYear'], + 'orderTypes' => ['ASC'], ]); $this->assertEquals(1944, $documents['body']['documents'][0]['releaseYear']); @@ -244,9 +243,8 @@ trait DatabaseBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'orderField' => 'releaseYear', - 'orderType' => 'DESC', - 'orderCast' => 'int', + 'orderAttributes' => ['releaseYear'], + 'orderTypes' => ['DESC'], ]); $this->assertEquals(1944, $documents['body']['documents'][2]['releaseYear']); @@ -267,9 +265,8 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'limit' => 1, - 'orderField' => 'releaseYear', - 'orderType' => 'ASC', - 'orderCast' => 'int', + 'orderAttributes' => ['releaseYear'], + 'orderTypes' => ['ASC'], ]); $this->assertEquals(1944, $documents['body']['documents'][0]['releaseYear']); @@ -281,9 +278,8 @@ trait DatabaseBase ], $this->getHeaders()), [ 'limit' => 2, 'offset' => 1, - 'orderField' => 'releaseYear', - 'orderType' => 'ASC', - 'orderCast' => 'int', + 'orderAttributes' => ['releaseYear'], + 'orderTypes' => ['ASC'], ]); $this->assertEquals(2017, $documents['body']['documents'][0]['releaseYear']); @@ -302,7 +298,7 @@ trait DatabaseBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'search' => 'Captain America', + 'queries' => ['title.search("Captain America")'], ]); $this->assertEquals(1944, $documents['body']['documents'][0]['releaseYear']); @@ -312,7 +308,7 @@ trait DatabaseBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'search' => 'Homecoming', + 'queries' => ['title.search("Homecoming")'], ]); $this->assertEquals(2017, $documents['body']['documents'][0]['releaseYear']); @@ -322,7 +318,7 @@ trait DatabaseBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'search' => 'spider', + 'queries' => ['title.search("spider")'], ]); $this->assertEquals(2019, $documents['body']['documents'][0]['releaseYear']); @@ -331,52 +327,53 @@ trait DatabaseBase return []; } + // TODO@kodumbeats test for empty searches and misformatted queries /** * @depends testCreateDocument */ - public function testListDocumentsFilters(array $data):array - { - $documents = $this->client->call(Client::METHOD_GET, '/database/collections/' . $data['moviesId'] . '/documents', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'filters' => [ - 'actors.firstName=Tom' - ], - ]); + // public function testListDocumentsFilters(array $data):array + // { + // $documents = $this->client->call(Client::METHOD_GET, '/database/collections/' . $data['moviesId'] . '/documents', array_merge([ + // 'content-type' => 'application/json', + // 'x-appwrite-project' => $this->getProject()['$id'], + // ], $this->getHeaders()), [ + // 'filters' => [ + // 'actors.firstName=Tom' + // ], + // ]); - $this->assertCount(2, $documents['body']['documents']); - $this->assertEquals('Spider-Man: Far From Home', $documents['body']['documents'][0]['name']); - $this->assertEquals('Spider-Man: Homecoming', $documents['body']['documents'][1]['name']); + // $this->assertCount(2, $documents['body']['documents']); + // $this->assertEquals('Spider-Man: Far From Home', $documents['body']['documents'][0]['name']); + // $this->assertEquals('Spider-Man: Homecoming', $documents['body']['documents'][1]['name']); - $documents = $this->client->call(Client::METHOD_GET, '/database/collections/' . $data['moviesId'] . '/documents', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'filters' => [ - 'releaseYear=1944' - ], - ]); + // $documents = $this->client->call(Client::METHOD_GET, '/database/collections/' . $data['moviesId'] . '/documents', array_merge([ + // 'content-type' => 'application/json', + // 'x-appwrite-project' => $this->getProject()['$id'], + // ], $this->getHeaders()), [ + // 'filters' => [ + // 'releaseYear=1944' + // ], + // ]); - $this->assertCount(1, $documents['body']['documents']); - $this->assertEquals('Captain America', $documents['body']['documents'][0]['name']); + // $this->assertCount(1, $documents['body']['documents']); + // $this->assertEquals('Captain America', $documents['body']['documents'][0]['name']); - $documents = $this->client->call(Client::METHOD_GET, '/database/collections/' . $data['moviesId'] . '/documents', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'filters' => [ - 'releaseYear!=1944' - ], - ]); + // $documents = $this->client->call(Client::METHOD_GET, '/database/collections/' . $data['moviesId'] . '/documents', array_merge([ + // 'content-type' => 'application/json', + // 'x-appwrite-project' => $this->getProject()['$id'], + // ], $this->getHeaders()), [ + // 'filters' => [ + // 'releaseYear!=1944' + // ], + // ]); - $this->assertCount(2, $documents['body']['documents']); - $this->assertEquals('Spider-Man: Far From Home', $documents['body']['documents'][0]['name']); - $this->assertEquals('Spider-Man: Homecoming', $documents['body']['documents'][1]['name']); + // $this->assertCount(2, $documents['body']['documents']); + // $this->assertEquals('Spider-Man: Far From Home', $documents['body']['documents'][0]['name']); + // $this->assertEquals('Spider-Man: Homecoming', $documents['body']['documents'][1]['name']); - return []; - } + // return []; + // } /** * @depends testCreateDocument From a62c46cc449eed9f5438bfd813b5b858d0c59723 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Tue, 15 Jun 2021 09:38:24 -0400 Subject: [PATCH 23/90] Fix tests for update and delete document --- app/controllers/api/database.php | 96 ++++++++++++-------- tests/e2e/Services/Database/DatabaseBase.php | 22 ++--- 2 files changed, 67 insertions(+), 51 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index b4467cba1..7fa2c15c5 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -36,17 +36,24 @@ App::post('/v1/database/collections') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_COLLECTION) - ->param('id', '', new Text(255), 'Collection ID. Max length: 255 chars.') + ->param('name', '', new Text(128), 'Collection name. Max length: 128 chars.') + ->param('read', null, new ArrayList(new Text(64)), 'An array of strings with read permissions. By default only the current user is granted with read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true) + ->param('write', null, new ArrayList(new Text(64)), 'An array of strings with write permissions. By default only the current user is granted with write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true) ->inject('response') ->inject('dbForExternal') ->inject('audits') - ->action(function ($id, $response, $dbForExternal, $audits) { + ->action(function ($name, $read, $write, $response, $dbForExternal, $audits) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForExternal*/ /** @var Appwrite\Event\Event $audits */ + + $id = $dbForExternal->getId(); $collection = $dbForExternal->createCollection($id); - $collection = $dbForExternal->getCollection($id); + + $collection->setAttribute('name', $name); + + $dbForExternal->updateDocument(Database2::COLLECTIONS, $id, $collection); $audits ->setParam('event', 'database.collections.create') @@ -652,14 +659,11 @@ App::post('/v1/database/collections/:collectionId/documents') ->param('data', [], new JSON(), 'Document data as JSON object.') ->param('read', null, new ArrayList(new Text(64)), 'An array of strings with read permissions. By default only the current user is granted with read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true) ->param('write', null, new ArrayList(new Text(64)), 'An array of strings with write permissions. By default only the current user is granted with write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true) - // ->param('parentDocument', '', new UID(), 'Parent document unique ID. Use when you want your new document to be a child of a parent document.', true) - // ->param('parentProperty', '', new Key(), 'Parent document property name. Use when you want your new document to be a child of a parent document.', true) - // ->param('parentPropertyType', Document::SET_TYPE_ASSIGN, new WhiteList([Document::SET_TYPE_ASSIGN, Document::SET_TYPE_APPEND, Document::SET_TYPE_PREPEND], true), 'Parent document property connection type. You can set this value to **assign**, **append** or **prepend**, default value is assign. Use when you want your new document to be a child of a parent document.', true) ->inject('response') ->inject('dbForExternal') ->inject('user') ->inject('audits') - ->action(function ($collectionId, $data, $read, $write, /*$parentDocument, $parentProperty, $parentPropertyType,*/ $response, $dbForExternal, $user, $audits) { + ->action(function ($collectionId, $data, $read, $write, $response, $dbForExternal, $user, $audits) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForExternal */ /** @var Utopia\Database\Document $user */ @@ -681,16 +685,28 @@ App::post('/v1/database/collections/:collectionId/documents') throw new Exception('Collection not found', 404); } - $data['$collection'] = $collectionId; // Adding this param to make API easier for developers + $data['$collection'] = $collection->getId(); // Adding this param to make API easier for developers + $data['$id'] = $dbForExternal->getId(); $data['$read'] = (is_null($read) && !$user->isEmpty()) ? ['user:'.$user->getId()] : $read ?? []; // By default set read permissions for user $data['$write'] = (is_null($write) && !$user->isEmpty()) ? ['user:'.$user->getId()] : $write ?? []; // By default set write permissions for user + // var_dump($collection->getAttributes()); + /** - * TODO@kodumbeats Set default collection values + * TODO@kodumbeats How to assign default values to attributes */ - // foreach ($collection->getAttribute('rules') as $key => $rule) { - // $key = $rule['key'] ?? ''; - // $default = $rule['default'] ?? null; + // foreach ($collection->getAttributes() as $key => $attribute) { + // $key = $attribute['$id'] ?? ''; + // if ($attribute['array'] === true) { + // $default = []; + // } elseif ($attribute['type'] === Database2::VAR_STRING) { + // $default = ''; + // } elseif ($attribute['type'] === Database2::VAR_BOOLEAN) { + // $default = false; + // } elseif ($attribute['type'] === Database2::VAR_INTEGER + // || $attribute['type'] === Database2::VAR_FLOAT) { + // $default = 0; + // } // if (!isset($data[$key])) { // $data[$key] = $default; @@ -719,8 +735,7 @@ App::get('/v1/database/collections/:collectionId/documents') ->groups(['api', 'database']) ->label('scope', 'documents.read') ->label('sdk.namespace', 'database') - ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) - ->label('sdk.method', 'listDocuments') + ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.method', 'listDocuments') ->label('sdk.description', '/docs/references/database/list-documents.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) @@ -811,58 +826,59 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId') ->param('read', null, new ArrayList(new Text(64)), 'An array of strings with read permissions. By default inherits the existing read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true) ->param('write', null, new ArrayList(new Text(64)), 'An array of strings with write permissions. By default inherits the existing write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true) ->inject('response') - ->inject('projectDB') + ->inject('dbForExternal') ->inject('audits') - ->action(function ($collectionId, $documentId, $data, $read, $write, $response, $projectDB, $audits) { + ->action(function ($collectionId, $documentId, $data, $read, $write, $response, $dbForExternal, $audits) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForExternal */ /** @var Appwrite\Event\Event $audits */ - $collection = $projectDB->getDocument($collectionId, false); - $document = $projectDB->getDocument($documentId, false); + $collection = $dbForExternal->getCollection($collectionId); - $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array - - if (!\is_array($data)) { - throw new Exception('Data param should be a valid JSON object', 400); - } - - if (\is_null($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) { + if ($collection->isEmpty()) { throw new Exception('Collection not found', 404); } - if (empty($document->getArrayCopy()) || $document->getCollection() != $collectionId) { // Check empty - throw new Exception('No document found', 404); + $document = $dbForExternal->getDocument($collectionId, $documentId); + + if ($document->isEmpty()) { + throw new Exception('Document not found', 404); + } + + $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array + + if (empty($data)) { + throw new Exception('Missing payload', 400); + } + + if (!\is_array($data)) { + throw new Exception('Data param should be a valid JSON object', 400); } $data = \array_merge($document->getArrayCopy(), $data); $data['$collection'] = $collection->getId(); // Make sure user don't switch collectionID $data['$id'] = $document->getId(); // Make sure user don't switch document unique ID - $data['$permissions']['read'] = (is_null($read)) ? ($document->getPermissions()['read'] ?? []) : $read; // By default inherit read permissions - $data['$permissions']['write'] = (is_null($write)) ? ($document->getPermissions()['write'] ?? []) : $write; // By default inherit write permissions - - if (empty($data)) { - throw new Exception('Missing payload', 400); - } + $data['$read'] = (is_null($read)) ? ($document->getRead() ?? []) : $read; // By default inherit read permissions + $data['$write'] = (is_null($write)) ? ($document->getWrite() ?? []) : $write; // By default inherit write permissions try { - $data = $projectDB->updateDocument($data); + $document = $dbForExternal->updateDocument($collection->getId(), $document->getId(), new Document2($data)); } catch (AuthorizationException $exception) { throw new Exception('Unauthorized permissions', 401); } catch (StructureException $exception) { throw new Exception('Bad structure. '.$exception->getMessage(), 400); } catch (\Exception $exception) { - throw new Exception('Failed saving document to DB', 500); + throw new Exception($exception->getMessage(), 400); } $audits ->setParam('event', 'database.documents.update') - ->setParam('resource', 'database/document/'.$data->getId()) - ->setParam('data', $data->getArrayCopy()) + ->setParam('resource', 'database/document/'.$document->getId()) + ->setParam('data', $document->getArrayCopy()) ; - $response->dynamic($data, Response::MODEL_DOCUMENT); + $response->dynamic2($document, Response::MODEL_DOCUMENT); }); App::delete('/v1/database/collections/:collectionId/documents/:documentId') @@ -879,7 +895,7 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId') ->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') ->param('documentId', null, new UID(), 'Document unique ID.') ->inject('response') - ->inject('projectDB') + ->inject('dbForExternal') ->inject('events') ->inject('audits') ->action(function ($collectionId, $documentId, $response, $dbForExternal, $events, $audits) { diff --git a/tests/e2e/Services/Database/DatabaseBase.php b/tests/e2e/Services/Database/DatabaseBase.php index c51e0f352..28360cae8 100644 --- a/tests/e2e/Services/Database/DatabaseBase.php +++ b/tests/e2e/Services/Database/DatabaseBase.php @@ -16,7 +16,7 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'id' => 'Movies', + 'name' => 'Movies', ]); $this->assertEquals($movies['headers']['status-code'], 201); @@ -385,8 +385,9 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'data' => [ - 'name' => 'Thor: Ragnaroc', + 'title' => 'Thor: Ragnaroc', 'releaseYear' => 2017, + 'actors' => [], ], 'read' => ['user:'.$this->getUser()['$id'], 'testx'], 'write' => ['user:'.$this->getUser()['$id'], 'testy'], @@ -396,24 +397,22 @@ trait DatabaseBase $collection = $document['body']['$collection']; $this->assertEquals($document['headers']['status-code'], 201); - $this->assertEquals($document['body']['name'], 'Thor: Ragnaroc'); + $this->assertEquals($document['body']['title'], 'Thor: Ragnaroc'); $this->assertEquals($document['body']['releaseYear'], 2017); - $this->assertEquals($document['body']['$permissions']['read'][1], 'testx'); - $this->assertEquals($document['body']['$permissions']['write'][1], 'testy'); + $this->assertEquals($document['body']['$read'][1], 'testx'); + $this->assertEquals($document['body']['$write'][1], 'testy'); $document = $this->client->call(Client::METHOD_PATCH, '/database/collections/' . $collection . '/documents/' . $id, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'data' => [ - 'name' => 'Thor: Ragnarok' + 'title' => 'Thor: Ragnarok', ], - 'read' => ['user:'.$this->getUser()['$id']], - 'write' => ['user:'.$this->getUser()['$id']], ]); $this->assertEquals($document['headers']['status-code'], 200); - $this->assertEquals($document['body']['name'], 'Thor: Ragnarok'); + $this->assertEquals($document['body']['title'], 'Thor: Ragnarok'); $this->assertEquals($document['body']['releaseYear'], 2017); $document = $this->client->call(Client::METHOD_GET, '/database/collections/' . $collection . '/documents/' . $id, array_merge([ @@ -425,7 +424,7 @@ trait DatabaseBase $collection = $document['body']['$collection']; $this->assertEquals($document['headers']['status-code'], 200); - $this->assertEquals($document['body']['name'], 'Thor: Ragnarok'); + $this->assertEquals($document['body']['title'], 'Thor: Ragnarok'); $this->assertEquals($document['body']['releaseYear'], 2017); return []; @@ -441,8 +440,9 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'data' => [ - 'name' => 'Thor: Ragnarok', + 'title' => 'Thor: Ragnarok', 'releaseYear' => 2017, + 'actors' => [], ], 'read' => ['user:'.$this->getUser()['$id']], 'write' => ['user:'.$this->getUser()['$id']], From 0e7c55c17ce9644e025a72ca1cbaa299ff7e9185 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Tue, 15 Jun 2021 10:24:51 -0400 Subject: [PATCH 24/90] Fix tests for deleteDocument --- app/controllers/api/database.php | 8 +- tests/e2e/Services/Database/DatabaseBase.php | 75 +++++++++---------- .../Database/DatabaseCustomServerTest.php | 2 +- 3 files changed, 40 insertions(+), 45 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 7fa2c15c5..dc6418dd2 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -16,7 +16,7 @@ use Appwrite\Database\Validator\Key; use Appwrite\Database\Validator\Structure; use Appwrite\Database\Validator\Collection; use Appwrite\Database\Validator\Authorization; -use Appwrite\Database\Exception\Authorization as AuthorizationException; +use Utopia\Database\Exception\Authorization as AuthorizationException; use Utopia\Database\Exception\Structure as StructureException; use Appwrite\Utopia\Response; use Utopia\Database\Database as Database2; @@ -690,8 +690,6 @@ App::post('/v1/database/collections/:collectionId/documents') $data['$read'] = (is_null($read) && !$user->isEmpty()) ? ['user:'.$user->getId()] : $read ?? []; // By default set read permissions for user $data['$write'] = (is_null($write) && !$user->isEmpty()) ? ['user:'.$user->getId()] : $write ?? []; // By default set write permissions for user - // var_dump($collection->getAttributes()); - /** * TODO@kodumbeats How to assign default values to attributes */ @@ -868,9 +866,7 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId') throw new Exception('Unauthorized permissions', 401); } catch (StructureException $exception) { throw new Exception('Bad structure. '.$exception->getMessage(), 400); - } catch (\Exception $exception) { - throw new Exception($exception->getMessage(), 400); - } + } $audits ->setParam('event', 'database.documents.update') diff --git a/tests/e2e/Services/Database/DatabaseBase.php b/tests/e2e/Services/Database/DatabaseBase.php index 28360cae8..1bfc0c9fb 100644 --- a/tests/e2e/Services/Database/DatabaseBase.php +++ b/tests/e2e/Services/Database/DatabaseBase.php @@ -487,7 +487,7 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'data' => [ - 'name' => 'Captain America', + 'title' => 'Captain America', 'releaseYear' => 1944, 'actors' => [], ], @@ -497,24 +497,23 @@ trait DatabaseBase $this->assertEquals($document['headers']['status-code'], 201); $this->assertEquals($document['body']['$collection'], $data['moviesId']); - $this->assertEquals($document['body']['name'], 'Captain America'); + $this->assertEquals($document['body']['title'], 'Captain America'); $this->assertEquals($document['body']['releaseYear'], 1944); - $this->assertIsArray($document['body']['$permissions']); - $this->assertIsArray($document['body']['$permissions']['read']); - $this->assertIsArray($document['body']['$permissions']['write']); + $this->assertIsArray($document['body']['$read']); + $this->assertIsArray($document['body']['$write']); if($this->getSide() == 'client') { - $this->assertCount(1, $document['body']['$permissions']['read']); - $this->assertCount(1, $document['body']['$permissions']['write']); - $this->assertEquals(['user:'.$this->getUser()['$id']], $document['body']['$permissions']['read']); - $this->assertEquals(['user:'.$this->getUser()['$id']], $document['body']['$permissions']['write']); + $this->assertCount(1, $document['body']['$read']); + $this->assertCount(1, $document['body']['$write']); + $this->assertEquals(['user:'.$this->getUser()['$id']], $document['body']['$read']); + $this->assertEquals(['user:'.$this->getUser()['$id']], $document['body']['$write']); } if($this->getSide() == 'server') { - $this->assertCount(0, $document['body']['$permissions']['read']); - $this->assertCount(0, $document['body']['$permissions']['write']); - $this->assertEquals([], $document['body']['$permissions']['read']); - $this->assertEquals([], $document['body']['$permissions']['write']); + $this->assertCount(0, $document['body']['$read']); + $this->assertCount(0, $document['body']['$write']); + $this->assertEquals([], $document['body']['$read']); + $this->assertEquals([], $document['body']['$write']); } // Updated and Inherit Permissions @@ -524,7 +523,7 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'data' => [ - 'name' => 'Captain America 2', + 'title' => 'Captain America 2', 'releaseYear' => 1945, 'actors' => [], ], @@ -532,21 +531,21 @@ trait DatabaseBase ]); $this->assertEquals($document['headers']['status-code'], 200); - $this->assertEquals($document['body']['name'], 'Captain America 2'); + $this->assertEquals($document['body']['title'], 'Captain America 2'); $this->assertEquals($document['body']['releaseYear'], 1945); if($this->getSide() == 'client') { - $this->assertCount(1, $document['body']['$permissions']['read']); - $this->assertCount(1, $document['body']['$permissions']['write']); - $this->assertEquals(['role:all'], $document['body']['$permissions']['read']); - $this->assertEquals(['user:'.$this->getUser()['$id']], $document['body']['$permissions']['write']); + $this->assertCount(1, $document['body']['$read']); + $this->assertCount(1, $document['body']['$write']); + $this->assertEquals(['role:all'], $document['body']['$read']); + $this->assertEquals(['user:'.$this->getUser()['$id']], $document['body']['$write']); } if($this->getSide() == 'server') { - $this->assertCount(1, $document['body']['$permissions']['read']); - $this->assertCount(0, $document['body']['$permissions']['write']); - $this->assertEquals(['role:all'], $document['body']['$permissions']['read']); - $this->assertEquals([], $document['body']['$permissions']['write']); + $this->assertCount(1, $document['body']['$read']); + $this->assertCount(0, $document['body']['$write']); + $this->assertEquals(['role:all'], $document['body']['$read']); + $this->assertEquals([], $document['body']['$write']); } $document = $this->client->call(Client::METHOD_GET, '/database/collections/' . $data['moviesId'] . '/documents/' . $id, array_merge([ @@ -555,21 +554,21 @@ trait DatabaseBase ], $this->getHeaders())); $this->assertEquals($document['headers']['status-code'], 200); - $this->assertEquals($document['body']['name'], 'Captain America 2'); + $this->assertEquals($document['body']['title'], 'Captain America 2'); $this->assertEquals($document['body']['releaseYear'], 1945); if($this->getSide() == 'client') { - $this->assertCount(1, $document['body']['$permissions']['read']); - $this->assertCount(1, $document['body']['$permissions']['write']); - $this->assertEquals(['role:all'], $document['body']['$permissions']['read']); - $this->assertEquals(['user:'.$this->getUser()['$id']], $document['body']['$permissions']['write']); + $this->assertCount(1, $document['body']['$read']); + $this->assertCount(1, $document['body']['$write']); + $this->assertEquals(['role:all'], $document['body']['$read']); + $this->assertEquals(['user:'.$this->getUser()['$id']], $document['body']['$write']); } if($this->getSide() == 'server') { - $this->assertCount(1, $document['body']['$permissions']['read']); - $this->assertCount(0, $document['body']['$permissions']['write']); - $this->assertEquals(['role:all'], $document['body']['$permissions']['read']); - $this->assertEquals([], $document['body']['$permissions']['write']); + $this->assertCount(1, $document['body']['$read']); + $this->assertCount(0, $document['body']['$write']); + $this->assertEquals(['role:all'], $document['body']['$read']); + $this->assertEquals([], $document['body']['$write']); } // Reset Permissions @@ -579,7 +578,7 @@ trait DatabaseBase 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'data' => [ - 'name' => 'Captain America 3', + 'title' => 'Captain America 3', 'releaseYear' => 1946, 'actors' => [], ], @@ -593,12 +592,12 @@ trait DatabaseBase if($this->getSide() == 'server') { $this->assertEquals($document['headers']['status-code'], 200); - $this->assertEquals($document['body']['name'], 'Captain America 3'); + $this->assertEquals($document['body']['title'], 'Captain America 3'); $this->assertEquals($document['body']['releaseYear'], 1946); - $this->assertCount(0, $document['body']['$permissions']['read']); - $this->assertCount(0, $document['body']['$permissions']['write']); - $this->assertEquals([], $document['body']['$permissions']['read']); - $this->assertEquals([], $document['body']['$permissions']['write']); + $this->assertCount(0, $document['body']['$read']); + $this->assertCount(0, $document['body']['$write']); + $this->assertEquals([], $document['body']['$read']); + $this->assertEquals([], $document['body']['$write']); } return $data; diff --git a/tests/e2e/Services/Database/DatabaseCustomServerTest.php b/tests/e2e/Services/Database/DatabaseCustomServerTest.php index 607b31ba1..48c0bbcd3 100644 --- a/tests/e2e/Services/Database/DatabaseCustomServerTest.php +++ b/tests/e2e/Services/Database/DatabaseCustomServerTest.php @@ -25,7 +25,7 @@ class DatabaseCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'id' => 'Actors', + 'name' => 'Actors', ]); $this->assertEquals($actors['headers']['status-code'], 201); From 53d3ae04196c57f952a8c0e3f97ee0e8fb0cc5e8 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Tue, 15 Jun 2021 10:25:14 -0400 Subject: [PATCH 25/90] Temporarily adjust client timeout --- tests/e2e/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/Client.php b/tests/e2e/Client.php index 82fd642a0..3bf4b164d 100644 --- a/tests/e2e/Client.php +++ b/tests/e2e/Client.php @@ -189,7 +189,7 @@ class Client curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36'); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0); - curl_setopt($ch, CURLOPT_TIMEOUT, 15); + curl_setopt($ch, CURLOPT_TIMEOUT, 60); curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders) { $len = strlen($header); $header = explode(':', $header, 2); From 829240e38cf0f3616e888819c2068f5194556557 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Wed, 16 Jun 2021 16:32:10 -0400 Subject: [PATCH 26/90] Add retry to first db connection and improve logging --- app/http.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/http.php b/app/http.php index 0e67e07c3..d0d8447e2 100644 --- a/app/http.php +++ b/app/http.php @@ -59,7 +59,15 @@ include __DIR__ . '/controllers/general.php'; $http->on('start', function (Server $http) use ($payloadSize, $register) { $app = new App('UTC'); - $dbForConsole = $app->getResource('dbForConsole'); /** @var Utopia\Database\Database $dbForConsole */ + + // Only retry connection once before throwing exception + try { + $dbForConsole = $app->getResource('dbForConsole'); /** @var Utopia\Database\Database $dbForConsole */ + } catch (\Exception $exception) { + Console::warning('[Setup] - Database not ready. Waiting for five seconds...'); + sleep(5); + $dbForConsole = $app->getResource('dbForConsole'); /** @var Utopia\Database\Database $dbForConsole */ + } if(!$dbForConsole->exists()) { Console::success('[Setup] - Server database init started...'); @@ -77,6 +85,8 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) { $adapter->setup(); foreach ($collections as $key => $collection) { + Console::success('[Setup] - Creating collection: ' . $collection['$id'] . '...'); + $dbForConsole->createCollection($key); foreach ($collection['attributes'] as $i => $attribute) { From 2530341bf9f31e54813c12d188ab5f7408001407 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Wed, 16 Jun 2021 16:36:18 -0400 Subject: [PATCH 27/90] Fix updateCollection and clean up code --- app/controllers/api/database.php | 69 +++++++++++--------------------- 1 file changed, 24 insertions(+), 45 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index dc6418dd2..db68a69d5 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -37,8 +37,8 @@ App::post('/v1/database/collections') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_COLLECTION) ->param('name', '', new Text(128), 'Collection name. Max length: 128 chars.') - ->param('read', null, new ArrayList(new Text(64)), 'An array of strings with read permissions. By default only the current user is granted with read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true) - ->param('write', null, new ArrayList(new Text(64)), 'An array of strings with write permissions. By default only the current user is granted with write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true) + ->param('read', null, new ArrayList(new Text(64)), 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.') + ->param('write', null, new ArrayList(new Text(64)), 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.') ->inject('response') ->inject('dbForExternal') ->inject('audits') @@ -51,7 +51,13 @@ App::post('/v1/database/collections') $collection = $dbForExternal->createCollection($id); + // TODO@kodumbeats what should the default permissions be? + $read = (is_null($read)) ? ($collection->getRead() ?? []) : $read; // By default inherit read permissions + $write = (is_null($write)) ? ($collection->getWrite() ?? []) : $write; // By default inherit write permissions + $collection->setAttribute('name', $name); + $collection->setAttribute('$read', $read); + $collection->setAttribute('$write', $write); $dbForExternal->updateDocument(Database2::COLLECTIONS, $id, $collection); @@ -135,57 +141,34 @@ App::put('/v1/database/collections/:collectionId') ->param('name', null, new Text(128), 'Collection name. Max length: 128 chars.') ->param('read', null, new ArrayList(new Text(64)), 'An array of strings with read permissions. By default inherits the existing read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true) ->param('write', null, new ArrayList(new Text(64)), 'An array of strings with write permissions. By default inherits the existing write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true) - ->param('rules', [], function ($projectDB) { return new ArrayList(new Collection($projectDB, [Database::SYSTEM_COLLECTION_RULES], ['$collection' => Database::SYSTEM_COLLECTION_RULES, '$permissions' => ['read' => [], 'write' => []]])); }, 'Array of [rule objects](/docs/rules). Each rule define a collection field name, data type and validation.', true, ['projectDB']) ->inject('response') - ->inject('projectDB') + ->inject('dbForExternal') ->inject('audits') - ->action(function ($collectionId, $name, $read, $write, $rules, $response, $projectDB, $audits) { + ->action(function ($collectionId, $name, $read, $write, $rules, $response, $dbForExternal, $audits) { /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Database\Database $dbForExternal */ /** @var Appwrite\Event\Event $audits */ - $collection = $projectDB->getDocument($collectionId, false); + $collection = $dbForExternal->getCollection($collectionId); - if (empty($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) { + if ($collection->isEmpty()) { throw new Exception('Collection not found', 404); } - $parsedRules = []; - $read = (is_null($read)) ? ($collection->getPermissions()['read'] ?? []) : $read; // By default inherit read permissions - $write = (is_null($write)) ? ($collection->getPermissions()['write'] ?? []) : $write; // By default inherit write permissions - - foreach ($rules as &$rule) { - $parsedRules[] = \array_merge([ - '$collection' => Database::SYSTEM_COLLECTION_RULES, - '$permissions' => [ - 'read' => $read, - 'write' => $write, - ], - ], $rule); - } + $read = (is_null($read)) ? ($collection->getRead() ?? []) : $read; // By default inherit read permissions + $write = (is_null($write)) ? ($collection->getWrite() ?? []) : $write; // By default inherit write permissions try { - $collection = $projectDB->updateDocument(\array_merge($collection->getArrayCopy(), [ + $collection = $dbForExternal->updateDocument(Database2::COLLECTIONS, $collection->getId(), new Document2(\array_merge($collection->getArrayCopy(), [ 'name' => $name, - 'structure' => true, - 'dateUpdated' => \time(), - '$permissions' => [ - 'read' => $read, - 'write' => $write, - ], - 'rules' => $parsedRules, - ])); + '$read' => $read, + '$write' => $write + ]))); } catch (AuthorizationException $exception) { throw new Exception('Unauthorized permissions', 401); } catch (StructureException $exception) { throw new Exception('Bad structure. '.$exception->getMessage(), 400); - } catch (\Exception $exception) { - throw new Exception('Failed saving document to DB', 500); - } - - if (false === $collection) { - throw new Exception('Failed saving collection to DB', 500); - } + } $audits ->setParam('event', 'database.collections.update') @@ -193,7 +176,7 @@ App::put('/v1/database/collections/:collectionId') ->setParam('data', $collection->getArrayCopy()) ; - $response->dynamic($collection, Response::MODEL_COLLECTION); + $response->dynamic2($collection, Response::MODEL_COLLECTION); }); App::delete('/v1/database/collections/:collectionId') @@ -261,12 +244,10 @@ App::post('/v1/database/collections/:collectionId/attributes') ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection using the Database service [server integration](/docs/server/database#createCollection).') ->param('id', '', new Text(256), 'Attribute ID.') ->param('type', null, new Text(256), 'Attribute type.') - // TODO@kodumbeats add units to description - ->param('size', null, new Numeric(), 'Attribute size.') + ->param('size', null, new Numeric(), 'Attribute size for text attributes, in number of characters. For integers, floats, or bools, use 0.') ->param('required', null, new Boolean(), 'Is attribute required?') ->param('signed', true, new Boolean(), 'Is attribute signed?', true) ->param('array', false, new Boolean(), 'Is attribute an array?', true) - // TODO@kodumbeats "We should have here a whitelist of allowed values. We should add a link to a reference in the future documentation. We might be able to hide this option from the public API for now." ->param('filters', [], new ArrayList(new Text(256)), 'Array of filters.', true) ->inject('response') ->inject('dbForExternal') @@ -282,7 +263,6 @@ App::post('/v1/database/collections/:collectionId/attributes') throw new Exception('Collection not found', 404); } - // TODO@kodumbeats handle failed attribute creation $success = $dbForExternal->createAttribute($collectionId, $id, $type, $size, $required, $signed, $array, $filters); // Database->createAttribute() does not return a document @@ -460,8 +440,7 @@ App::post('/v1/database/collections/:collectionId/indexes') ->label('sdk.response.model', Response::MODEL_INDEX) ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') ->param('id', null, new Text(256), 'Index ID.') - // TODO@kodumbeats type should be a whitelist of supported index types - ->param('type', null, new Text(256), 'Index type.') + ->param('type', null, new WhiteList([Database2::INDEX_KEY, Database2::INDEX_FULLTEXT, Database2::INDEX_UNIQUE, Database2::INDEX_SPATIAL, Database2::INDEX_ARRAY]), 'Index type.') ->param('attributes', null, new ArrayList(new Text(256)), 'Array of attributes to index.') ->param('lengths', [], new ArrayList(new Text(256)), 'Array of index lengths.', true) ->param('orders', [], new ArrayList(new Text(256)), 'Array of index orders.', true) @@ -550,7 +529,7 @@ App::get('v1/database/collections/:collectionId/indexes/:indexId') ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') ->param('indexId', null, new Text(256), 'Index ID.') ->inject('response') - ->inject('projectDB') + ->inject('dbForExternal') ->action(function ($collectionId, $indexId, $response, $dbForExternal) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForExternal */ From 9fb64c5621bc3cb812f8ace6a4119a65e1ab4d3a Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 17 Jun 2021 11:15:14 -0400 Subject: [PATCH 28/90] createCollection requires read/write params --- tests/e2e/Services/Database/DatabaseBase.php | 2 ++ tests/e2e/Services/Database/DatabaseCustomServerTest.php | 2 ++ 2 files changed, 4 insertions(+) diff --git a/tests/e2e/Services/Database/DatabaseBase.php b/tests/e2e/Services/Database/DatabaseBase.php index 1bfc0c9fb..d75349b9d 100644 --- a/tests/e2e/Services/Database/DatabaseBase.php +++ b/tests/e2e/Services/Database/DatabaseBase.php @@ -17,6 +17,8 @@ trait DatabaseBase 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ 'name' => 'Movies', + 'read' => ['role:all'], + 'write' => ['role:all'], ]); $this->assertEquals($movies['headers']['status-code'], 201); diff --git a/tests/e2e/Services/Database/DatabaseCustomServerTest.php b/tests/e2e/Services/Database/DatabaseCustomServerTest.php index 48c0bbcd3..5c644c021 100644 --- a/tests/e2e/Services/Database/DatabaseCustomServerTest.php +++ b/tests/e2e/Services/Database/DatabaseCustomServerTest.php @@ -26,6 +26,8 @@ class DatabaseCustomServerTest extends Scope 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ 'name' => 'Actors', + 'read' => ['role:all'], + 'write' => ['role:all'], ]); $this->assertEquals($actors['headers']['status-code'], 201); From a54af88a38a37d76f9d599254b03a4502ec9fa57 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 17 Jun 2021 11:53:06 -0400 Subject: [PATCH 29/90] Address todo items for attributes and index routes --- app/controllers/api/database.php | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index db68a69d5..4f91f46df 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -312,9 +312,14 @@ App::get('v1/database/collections/:collectionId/attributes') throw new Exception('Collection not found', 404); } - // TODO@kodumbeats array_merge collectionId to each attribute $attributes = $collection->getAttributes(); + $attributes = array_map(function ($attribute) use ($collection) { + return new Document2([\array_merge($attribute, [ + 'collectionId' => $collection->getId(), + ])]); + }, $attributes); + $response->dynamic2(new Document2([ 'sum' => \count($attributes), 'attributes' => $attributes @@ -506,9 +511,14 @@ App::get('v1/database/collections/:collectionId/indexes') throw new Exception('Collection not found', 404); } - // TODO@kodumbeats decode index string and merge ['$collection' => $collectionId] $indexes = $collection->getAttribute('indexes'); + $indexes = array_map(function ($index) use ($collection) { + return new Document2([\array_merge($index, [ + 'collectionId' => $collection->getId(), + ])]); + }, $indexes); + $response->dynamic2(new Document2([ 'sum' => \count($indexes), 'attributes' => $indexes, @@ -540,7 +550,6 @@ App::get('v1/database/collections/:collectionId/indexes/:indexId') throw new Exception('Collection not found', 404); } - // TODO@kodumbeats decode 'indexes' into array $indexes = $collection->getAttribute('indexes'); // // Search for index @@ -587,7 +596,6 @@ App::delete('/v1/database/collections/:collectionId/indexes/:indexId') throw new Exception('Collection not found', 404); } - // TODO@kodumbeats decode 'indexes' into array $indexes = $collection->getAttribute('indexes'); // // Search for index @@ -719,14 +727,10 @@ App::get('/v1/database/collections/:collectionId/documents') ->label('sdk.response.model', Response::MODEL_DOCUMENT_LIST) ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') ->param('queries', [], new ArrayList(new Text(128)), 'Array of query strings.', true) - // ->param('queries', [], new Text(128), 'Array of query strings.', true) ->param('limit', 25, new Range(0, 100), 'Maximum number of documents to return in response. Use this value to manage pagination. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) ->param('offset', 0, new Range(0, 900000000), 'Offset value. The default value is 0. Use this param to manage pagination.', true) ->param('orderAttributes', [], new ArrayList(new Text(128)), 'Array of attributes used to sort results.', true) ->param('orderTypes', [], new ArrayList(new WhiteList(['DESC', 'ASC'], true)), 'Array of order directions for sorting attribtues. Possible values are DESC for descending order, or ASC for ascending order.', true) - // ->param('orderField', '', new Text(128), 'Document field that results will be sorted by.', true) - // ->param('orderCast', 'string', new WhiteList(['int', 'string', 'date', 'time', 'datetime'], true), 'Order field type casting. Possible values are int, string, date, time or datetime. The database will attempt to cast the order field to the value you pass here. The default value is a string.', true) - // ->param('search', '', new Text(256), 'Search query. Enter any free text search. The database will try to find a match against all document attributes and children. Max length: 256 chars.', true) ->inject('response') ->inject('dbForExternal') ->action(function ($collectionId, $queries, $limit, $offset, $orderAttributes, $orderTypes, $response, $dbForExternal) { From 435f6879f206c8a3f215bff09763600ad5e076f6 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 17 Jun 2021 13:03:24 -0400 Subject: [PATCH 30/90] Remove unused imports --- app/controllers/api/database.php | 53 ++++++++++++++------------------ 1 file changed, 23 insertions(+), 30 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 4f91f46df..9e1f43f5c 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -9,20 +9,13 @@ use Utopia\Validator\WhiteList; use Utopia\Validator\Text; use Utopia\Validator\ArrayList; use Utopia\Validator\JSON; -use Appwrite\Database\Database; -use Appwrite\Database\Document; use Appwrite\Database\Validator\UID; -use Appwrite\Database\Validator\Key; -use Appwrite\Database\Validator\Structure; -use Appwrite\Database\Validator\Collection; -use Appwrite\Database\Validator\Authorization; use Utopia\Database\Exception\Authorization as AuthorizationException; use Utopia\Database\Exception\Structure as StructureException; use Appwrite\Utopia\Response; -use Utopia\Database\Database as Database2; -use Utopia\Database\Document as Document2; +use Utopia\Database\Database; +use Utopia\Database\Document; use Utopia\Database\Query; -use Utopia\Database\Validator\Authorization as Authorization2; App::post('/v1/database/collections') ->desc('Create Collection') @@ -59,7 +52,7 @@ App::post('/v1/database/collections') $collection->setAttribute('$read', $read); $collection->setAttribute('$write', $write); - $dbForExternal->updateDocument(Database2::COLLECTIONS, $id, $collection); + $dbForExternal->updateDocument(Database::COLLECTIONS, $id, $collection); $audits ->setParam('event', 'database.collections.create') @@ -92,7 +85,7 @@ App::get('/v1/database/collections') $collections = $dbForExternal->listCollections($limit, $offset); - $response->dynamic2(new Document2([ + $response->dynamic2(new Document([ 'collections' => $collections, 'sum' => \count($collections), ]), Response::MODEL_COLLECTION_LIST); @@ -159,7 +152,7 @@ App::put('/v1/database/collections/:collectionId') $write = (is_null($write)) ? ($collection->getWrite() ?? []) : $write; // By default inherit write permissions try { - $collection = $dbForExternal->updateDocument(Database2::COLLECTIONS, $collection->getId(), new Document2(\array_merge($collection->getArrayCopy(), [ + $collection = $dbForExternal->updateDocument(Database::COLLECTIONS, $collection->getId(), new Document(\array_merge($collection->getArrayCopy(), [ 'name' => $name, '$read' => $read, '$write' => $write @@ -267,7 +260,7 @@ App::post('/v1/database/collections/:collectionId/attributes') // Database->createAttribute() does not return a document // So we need to create one for the response - $attribute = new Document2([ + $attribute = new Document([ '$collection' => $collectionId, '$id' => $id, 'type' => $type, @@ -315,12 +308,12 @@ App::get('v1/database/collections/:collectionId/attributes') $attributes = $collection->getAttributes(); $attributes = array_map(function ($attribute) use ($collection) { - return new Document2([\array_merge($attribute, [ + return new Document([\array_merge($attribute, [ 'collectionId' => $collection->getId(), ])]); }, $attributes); - $response->dynamic2(new Document2([ + $response->dynamic2(new Document([ 'sum' => \count($attributes), 'attributes' => $attributes ]), Response::MODEL_ATTRIBUTE_LIST); @@ -360,7 +353,7 @@ App::get('v1/database/collections/:collectionId/attributes/:attributeId') throw new Exception('Attribute not found', 404); } - $attribute = new Document2([\array_merge($attributes[$attributeIndex], [ + $attribute = new Document([\array_merge($attributes[$attributeIndex], [ 'collectionId' => $collectionId, ])]); @@ -406,7 +399,7 @@ App::delete('/v1/database/collections/:collectionId/attributes/:attributeId') throw new Exception('Attribute not found', 404); } - $attribute = new Document2([\array_merge($attributes[$attributeIndex], [ + $attribute = new Document([\array_merge($attributes[$attributeIndex], [ 'collectionId' => $collectionId, ])]); @@ -445,7 +438,7 @@ App::post('/v1/database/collections/:collectionId/indexes') ->label('sdk.response.model', Response::MODEL_INDEX) ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') ->param('id', null, new Text(256), 'Index ID.') - ->param('type', null, new WhiteList([Database2::INDEX_KEY, Database2::INDEX_FULLTEXT, Database2::INDEX_UNIQUE, Database2::INDEX_SPATIAL, Database2::INDEX_ARRAY]), 'Index type.') + ->param('type', null, new WhiteList([Database::INDEX_KEY, Database::INDEX_FULLTEXT, Database::INDEX_UNIQUE, Database::INDEX_SPATIAL, Database::INDEX_ARRAY]), 'Index type.') ->param('attributes', null, new ArrayList(new Text(256)), 'Array of attributes to index.') ->param('lengths', [], new ArrayList(new Text(256)), 'Array of index lengths.', true) ->param('orders', [], new ArrayList(new Text(256)), 'Array of index orders.', true) @@ -467,7 +460,7 @@ App::post('/v1/database/collections/:collectionId/indexes') // Database->createIndex() does not return a document // So we need to create one for the response - $index = new Document2([ + $index = new Document([ '$collection' => $collectionId, '$id' => $id, 'type' => $type, @@ -514,12 +507,12 @@ App::get('v1/database/collections/:collectionId/indexes') $indexes = $collection->getAttribute('indexes'); $indexes = array_map(function ($index) use ($collection) { - return new Document2([\array_merge($index, [ + return new Document([\array_merge($index, [ 'collectionId' => $collection->getId(), ])]); }, $indexes); - $response->dynamic2(new Document2([ + $response->dynamic2(new Document([ 'sum' => \count($indexes), 'attributes' => $indexes, ]), Response::MODEL_INDEX_LIST); @@ -559,7 +552,7 @@ App::get('v1/database/collections/:collectionId/indexes/:indexId') throw new Exception('Index not found', 404); } - $index = new Document2([\array_merge($indexes[$indexIndex], [ + $index = new Document([\array_merge($indexes[$indexIndex], [ 'collectionId' => $collectionId, ])]); @@ -605,7 +598,7 @@ App::delete('/v1/database/collections/:collectionId/indexes/:indexId') throw new Exception('Index not found', 404); } - $index = new Document2([\array_merge($indexes[$indexIndex], [ + $index = new Document([\array_merge($indexes[$indexIndex], [ 'collectionId' => $collectionId, ])]); @@ -684,12 +677,12 @@ App::post('/v1/database/collections/:collectionId/documents') // $key = $attribute['$id'] ?? ''; // if ($attribute['array'] === true) { // $default = []; - // } elseif ($attribute['type'] === Database2::VAR_STRING) { + // } elseif ($attribute['type'] === Database::VAR_STRING) { // $default = ''; - // } elseif ($attribute['type'] === Database2::VAR_BOOLEAN) { + // } elseif ($attribute['type'] === Database::VAR_BOOLEAN) { // $default = false; - // } elseif ($attribute['type'] === Database2::VAR_INTEGER - // || $attribute['type'] === Database2::VAR_FLOAT) { + // } elseif ($attribute['type'] === Database::VAR_INTEGER + // || $attribute['type'] === Database::VAR_FLOAT) { // $default = 0; // } @@ -700,7 +693,7 @@ App::post('/v1/database/collections/:collectionId/documents') // TODO@kodumbeats catch other exceptions try { - $document = $dbForExternal->createDocument($collectionId, new Document2($data)); + $document = $dbForExternal->createDocument($collectionId, new Document($data)); } catch (StructureException $exception) { throw new Exception($exception->getMessage(), 400); } @@ -749,7 +742,7 @@ App::get('/v1/database/collections/:collectionId/documents') $documents = $dbForExternal->find($collectionId, $queries, $limit, $offset, $orderAttributes, $orderTypes); - $response->dynamic2(new Document2([ + $response->dynamic2(new Document([ 'sum' => \count($documents), 'documents' => $documents, ]), Response::MODEL_DOCUMENT_LIST); @@ -844,7 +837,7 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId') $data['$write'] = (is_null($write)) ? ($document->getWrite() ?? []) : $write; // By default inherit write permissions try { - $document = $dbForExternal->updateDocument($collection->getId(), $document->getId(), new Document2($data)); + $document = $dbForExternal->updateDocument($collection->getId(), $document->getId(), new Document($data)); } catch (AuthorizationException $exception) { throw new Exception('Unauthorized permissions', 401); } catch (StructureException $exception) { From 160330675af44b761f00bffb92a4981452cd0a93 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 17 Jun 2021 13:09:40 -0400 Subject: [PATCH 31/90] Add whitelist of filters to createAttribute --- 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 9e1f43f5c..771c43014 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -241,7 +241,7 @@ App::post('/v1/database/collections/:collectionId/attributes') ->param('required', null, new Boolean(), 'Is attribute required?') ->param('signed', true, new Boolean(), 'Is attribute signed?', true) ->param('array', false, new Boolean(), 'Is attribute an array?', true) - ->param('filters', [], new ArrayList(new Text(256)), 'Array of filters.', true) + ->param('filters', [], new ArrayList(new Whitelist(['encrypt', 'json'])), 'Array of filters.', true) ->inject('response') ->inject('dbForExternal') ->inject('audits') From 90b280a014f8011c8d133703a8320efa4a0031c4 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 17 Jun 2021 14:22:06 -0400 Subject: [PATCH 32/90] Outline database resque worker --- app/workers/database.php | 37 ++++++++++++++++++++++++++++++++++++ bin/worker-database | 10 ++++++++++ src/Appwrite/Event/Event.php | 3 +++ 3 files changed, 50 insertions(+) create mode 100644 app/workers/database.php create mode 100644 bin/worker-database diff --git a/app/workers/database.php b/app/workers/database.php new file mode 100644 index 000000000..38e16e518 --- /dev/null +++ b/app/workers/database.php @@ -0,0 +1,37 @@ + Date: Thu, 17 Jun 2021 14:22:43 -0400 Subject: [PATCH 33/90] Add reminder to filter results from listCollections --- app/controllers/api/database.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 771c43014..d1cbb9115 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -84,6 +84,7 @@ App::get('/v1/database/collections') /** @var Utopia\Database\Database $dbForExternal */ $collections = $dbForExternal->listCollections($limit, $offset); + // TODO@kodumbeats allow for filtering collections $response->dynamic2(new Document([ 'collections' => $collections, From e278b06e0646599fed7a8a58165706e3b334dea3 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 17 Jun 2021 15:30:03 -0400 Subject: [PATCH 34/90] Organize used libraries --- app/workers/database.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/workers/database.php b/app/workers/database.php index 38e16e518..0373da18f 100644 --- a/app/workers/database.php +++ b/app/workers/database.php @@ -1,18 +1,18 @@ Date: Thu, 17 Jun 2021 15:30:28 -0400 Subject: [PATCH 35/90] Add getters for dbForInternal and dbForExternal --- app/workers/database.php | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/app/workers/database.php b/app/workers/database.php index 0373da18f..6294f228b 100644 --- a/app/workers/database.php +++ b/app/workers/database.php @@ -34,4 +34,36 @@ class DeletesV1 extends Worker public function shutdown(): void { } + + /** + * @param string $projectId + * + * @return Database + */ + protected function getInternalDB($projectId): Database + { + global $register; + + $cache = new Cache(new RedisCache($register->get('cache'))); + $dbForInternal = new Database(new MariaDB($register->get('db')), $cache); + $dbForInternal->setNamespace('project_'.$projectId.'_internal'); // Main DB + + return $dbForInternal; + } + + /** + * @param string $projectId + * + * @return Database + */ + protected function getExternalDB($projectId): Database + { + global $register; + + $cache = new Cache(new RedisCache($register->get('cache'))); + $dbForExternal = new Database(new MariaDB($register->get('db')), $cache); + $dbForExternal->setNamespace('project_'.$projectId.'_external'); // Main DB + + return $dbForExternal; + } } From 707f49b83fac46c77cb10acb5ce370aa7d148eb2 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 17 Jun 2021 17:58:35 -0400 Subject: [PATCH 36/90] Set up database worker --- app/init.php | 9 +++++++++ app/workers/database.php | 6 +++++- docker-compose.yml | 29 +++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/app/init.php b/app/init.php index f79ef3270..d62e50af3 100644 --- a/app/init.php +++ b/app/init.php @@ -64,7 +64,12 @@ const APP_SOCIAL_DISCORD = 'https://appwrite.io/discord'; const APP_SOCIAL_DISCORD_CHANNEL = '564160730845151244'; const APP_SOCIAL_DEV = 'https://dev.to/appwrite'; const APP_SOCIAL_STACKSHARE = 'https://stackshare.io/appwrite'; +// Creation Types +const CREATE_TYPE_ATTRIBUTE = 'newAttribute'; +const CREATE_TYPE_INDEX = 'newIndex'; // Deletion Types +const DELETE_TYPE_ATTRIBUTE = 'attribute'; +const DELETE_TYPE_INDEX = 'index'; const DELETE_TYPE_DOCUMENT = 'document'; const DELETE_TYPE_EXECUTIONS = 'executions'; const DELETE_TYPE_AUDIT = 'audit'; @@ -375,6 +380,10 @@ App::setResource('deletes', function($register) { return new Event(Event::DELETE_QUEUE_NAME, Event::DELETE_CLASS_NAME); }, ['register']); +App::setResource('database', function($register) { + return new Event(Event::DATABASE_QUEUE_NAME, Event::DATABASE_CLASS_NAME); +}, ['register']); + // Test Mock App::setResource('clients', function($request, $console, $project) { $console->setAttribute('platforms', [ // Allways allow current host diff --git a/app/workers/database.php b/app/workers/database.php index 6294f228b..c7426195d 100644 --- a/app/workers/database.php +++ b/app/workers/database.php @@ -19,7 +19,7 @@ require_once __DIR__.'/../init.php'; Console::title('Database V1 Worker'); Console::success(APP_NAME.' database worker v1 has started'."\n"); -class DeletesV1 extends Worker +class DatabaseV1 extends Worker { public $args = []; @@ -29,6 +29,10 @@ class DeletesV1 extends Worker public function run(): void { + $collections = Config::getParam('collections2'); + + var_dump($collections); + } public function shutdown(): void diff --git a/docker-compose.yml b/docker-compose.yml index be056b8a6..8fc1bff54 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -240,6 +240,35 @@ services: - _APP_DB_USER - _APP_DB_PASS + appwrite-worker-database: + entrypoint: worker-database + container_name: appwrite-worker-database + build: + context: . + networks: + - appwrite + volumes: + - appwrite-uploads:/storage/uploads:rw + - appwrite-cache:/storage/cache:rw + - appwrite-functions:/storage/functions:rw + - appwrite-certificates:/storage/certificates:rw + - ./app:/usr/src/code/app + - ./src:/usr/src/code/src + depends_on: + - redis + - mariadb + environment: + - _APP_ENV + - _APP_REDIS_HOST + - _APP_REDIS_PORT + - _APP_REDIS_USER + - _APP_REDIS_PASS + - _APP_DB_HOST + - _APP_DB_PORT + - _APP_DB_SCHEMA + - _APP_DB_USER + - _APP_DB_PASS + appwrite-worker-certificates: entrypoint: worker-certificates container_name: appwrite-worker-certificates From 4a4165a304be9e98bbd70590d8c7c3b28fce8b33 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 17 Jun 2021 18:13:46 -0400 Subject: [PATCH 37/90] Make worker-database executable --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 09cbd677a..6239cbf47 100755 --- a/Dockerfile +++ b/Dockerfile @@ -212,6 +212,7 @@ RUN chmod +x /usr/local/bin/doctor && \ chmod +x /usr/local/bin/vars && \ chmod +x /usr/local/bin/worker-audits && \ chmod +x /usr/local/bin/worker-certificates && \ + chmod +x /usr/local/bin/worker-database && \ chmod +x /usr/local/bin/worker-deletes && \ chmod +x /usr/local/bin/worker-functions && \ chmod +x /usr/local/bin/worker-mails && \ From afd347ca6f3248c01040543a760fa6a105f3ae9f Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 18 Jun 2021 12:13:37 -0400 Subject: [PATCH 38/90] Create attributes and indexes using database worker --- app/controllers/api/database.php | 22 +++++-- app/controllers/shared/api.php | 17 ++++-- app/workers/database.php | 102 ++++++++++++++++++++++++++++++- 3 files changed, 130 insertions(+), 11 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index d1cbb9115..44d02d720 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -245,8 +245,9 @@ App::post('/v1/database/collections/:collectionId/attributes') ->param('filters', [], new ArrayList(new Whitelist(['encrypt', 'json'])), 'Array of filters.', true) ->inject('response') ->inject('dbForExternal') + ->inject('database') ->inject('audits') - ->action(function ($collectionId, $id, $type, $size, $required, $signed, $array, $filters, $response, $dbForExternal, $audits) { + ->action(function ($collectionId, $id, $type, $size, $required, $signed, $array, $filters, $response, $dbForExternal, $database, $audits) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForExternal*/ /** @var Appwrite\Event\Event $audits */ @@ -257,9 +258,9 @@ App::post('/v1/database/collections/:collectionId/attributes') throw new Exception('Collection not found', 404); } - $success = $dbForExternal->createAttribute($collectionId, $id, $type, $size, $required, $signed, $array, $filters); + $success = $dbForExternal->addAttributeInQueue($collectionId, $id, $type, $size, $required, $signed, $array, $filters); - // Database->createAttribute() does not return a document + // Database->addAttributeInQueue() does not return a document // So we need to create one for the response $attribute = new Document([ '$collection' => $collectionId, @@ -272,6 +273,11 @@ App::post('/v1/database/collections/:collectionId/attributes') 'filters' => $filters ]); + $database + ->setParam('type', CREATE_TYPE_ATTRIBUTE) + ->setParam('document', $attribute) + ; + $audits ->setParam('event', 'database.attributes.create') ->setParam('resource', 'database/attributes/'.$attribute->getId()) @@ -445,8 +451,9 @@ App::post('/v1/database/collections/:collectionId/indexes') ->param('orders', [], new ArrayList(new Text(256)), 'Array of index orders.', true) ->inject('response') ->inject('dbForExternal') + ->inject('database') ->inject('audits') - ->action(function ($collectionId, $id, $type, $attributes, $lengths, $orders, $response, $dbForExternal, $audits) { + ->action(function ($collectionId, $id, $type, $attributes, $lengths, $orders, $response, $dbForExternal, $database, $audits) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForExternal */ /** @var Appwrite\Event\Event $audits */ @@ -457,7 +464,7 @@ App::post('/v1/database/collections/:collectionId/indexes') throw new Exception('Collection not found', 404); } - $success = $dbForExternal->createIndex($collectionId, $id, $type, $attributes, $lengths, $orders); + $success = $dbForExternal->addIndexInQueue($collectionId, $id, $type, $attributes, $lengths, $orders); // Database->createIndex() does not return a document // So we need to create one for the response @@ -470,6 +477,11 @@ App::post('/v1/database/collections/:collectionId/indexes') 'orders' => $orders, ]); + $database + ->setParam('type', CREATE_TYPE_INDEX) + ->setParam('document', $index) + ; + $audits ->setParam('event', 'database.indexes.create') ->setParam('resource', 'database/indexes/'.$index->getId()) diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 14555b502..0d24150a6 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -9,7 +9,7 @@ use Utopia\Abuse\Adapters\TimeLimit; use Utopia\Storage\Device\Local; use Utopia\Storage\Storage; -App::init(function ($utopia, $request, $response, $project, $user, $register, $events, $audits, $usage, $deletes, $dbForInternal) { +App::init(function ($utopia, $request, $response, $project, $user, $register, $events, $audits, $usage, $deletes, $database, $dbForInternal) { /** @var Utopia\App $utopia */ /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ @@ -20,6 +20,7 @@ App::init(function ($utopia, $request, $response, $project, $user, $register, $e /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Event\Event $usage */ /** @var Appwrite\Event\Event $deletes */ + /** @var Appwrite\Event\Event $database */ /** @var Appwrite\Event\Event $functions */ /** @var Utopia\Database\Database $dbForInternal */ @@ -109,7 +110,10 @@ App::init(function ($utopia, $request, $response, $project, $user, $register, $e ->setParam('projectId', $project->getId()) ; -}, ['utopia', 'request', 'response', 'project', 'user', 'register', 'events', 'audits', 'usage', 'deletes', 'dbForInternal'], 'api'); + $database + ->setParam('projectId', $project->getId()) + ; +}, ['utopia', 'request', 'response', 'project', 'user', 'register', 'events', 'audits', 'usage', 'deletes', 'database', 'dbForInternal'], 'api'); App::init(function ($utopia, $request, $response, $project, $user) { @@ -166,7 +170,7 @@ App::init(function ($utopia, $request, $response, $project, $user) { }, ['utopia', 'request', 'response', 'project', 'user'], 'auth'); -App::shutdown(function ($utopia, $request, $response, $project, $events, $audits, $usage, $deletes, $mode) { +App::shutdown(function ($utopia, $request, $response, $project, $events, $audits, $usage, $deletes, $database, $mode) { /** @var Utopia\App $utopia */ /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ @@ -175,6 +179,7 @@ App::shutdown(function ($utopia, $request, $response, $project, $events, $audits /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Event\Event $usage */ /** @var Appwrite\Event\Event $deletes */ + /** @var Appwrite\Event\Event $database */ /** @var Appwrite\Event\Event $functions */ /** @var bool $mode */ @@ -204,6 +209,10 @@ App::shutdown(function ($utopia, $request, $response, $project, $events, $audits if (!empty($deletes->getParam('type')) && !empty($deletes->getParam('document'))) { $deletes->trigger(); } + + if (!empty($database->getParam('type')) && !empty($database->getParam('document'))) { + $database->trigger(); + } $route = $utopia->match($request); if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled' @@ -218,4 +227,4 @@ App::shutdown(function ($utopia, $request, $response, $project, $events, $audits ; } -}, ['utopia', 'request', 'response', 'project', 'events', 'audits', 'usage', 'deletes', 'mode'], 'api'); +}, ['utopia', 'request', 'response', 'project', 'events', 'audits', 'usage', 'deletes', 'database', 'mode'], 'api'); diff --git a/app/workers/database.php b/app/workers/database.php index c7426195d..727f931a5 100644 --- a/app/workers/database.php +++ b/app/workers/database.php @@ -29,9 +29,65 @@ class DatabaseV1 extends Worker public function run(): void { - $collections = Config::getParam('collections2'); + $projectId = $this->args['projectId'] ?? ''; + $type = $this->args['type'] ?? ''; + + switch (strval($type)) { + case CREATE_TYPE_ATTRIBUTE: + $attribute = $this->args['document'] ?? ''; + $attribute = new Document($attribute); + $this->createAttribute($attribute, $projectId); + break; + case CREATE_TYPE_INDEX: + $attribute = $this->args['document'] ?? ''; + $attribute = new Document($attribute); + $this->createAttribute($attribute, $projectId); + break; - var_dump($collections); + // case DELETE_TYPE_DOCUMENT: + // $document = $this->args['document'] ?? ''; + // $document = new Document($document); + + // switch ($document->getCollection()) { + // case Database::SYSTEM_COLLECTION_PROJECTS: + // $this->deleteProject($document); + // break; + // case Database::SYSTEM_COLLECTION_FUNCTIONS: + // $this->deleteFunction($document, $projectId); + // break; + // case Database::SYSTEM_COLLECTION_USERS: + // $this->deleteUser($document, $projectId); + // break; + // case Database::SYSTEM_COLLECTION_COLLECTIONS: + // $this->deleteDocuments($document, $projectId); + // break; + // default: + // Console::error('No lazy delete operation available for document of type: '.$document->getCollection()); + // break; + // } + // break; + + // case DELETE_TYPE_EXECUTIONS: + // $this->deleteExecutionLogs($this->args['timestamp']); + // break; + + // case DELETE_TYPE_AUDIT: + // $this->deleteAuditLogs($this->args['timestamp']); + // break; + + // case DELETE_TYPE_ABUSE: + // $this->deleteAbuseLogs($this->args['timestamp']); + // break; + + // case DELETE_TYPE_CERTIFICATES: + // $document = new Document($this->args['document']); + // $this->deleteCertificates($document); + // break; + + default: + Console::error('No database operation for type: '.$type); + break; + } } @@ -39,6 +95,48 @@ class DatabaseV1 extends Worker { } + /** + * @param Document $attribute + */ + protected function createAttribute($attribute, $projectId) + { + $dbForExternal = $this->getExternalDB($projectId); + + $collectionId = $attribute->getCollection(); + $id = $attribute->getAttribute('$id'); + $type = $attribute->getAttribute('type'); + $size = $attribute->getAttribute('size'); + $required = $attribute->getAttribute('required'); + $signed = $attribute->getAttribute('signed'); + $array = $attribute->getAttribute('array'); + $filters = $attribute->getAttribute('filters'); + + $success = $dbForExternal->createAttribute($collectionId, $id, $type, $size, $required, $signed, $array, $filters); + if ($success) { + $removed = $dbForExternal->removeAttributeInQueue($collectionId, $id); + } + } + + /** + * @param Document $index + */ + protected function createIndex($index, $projectId) + { + $dbForExternal = $this->getExternalDB($projectId); + + $collectionId = $index->getCollection(); + $id = $index->getAttribute('$id'); + $type = $index->getAttribute('type'); + $attributes = $index->getAttribute('attributes'); + $lengths = $index->getAttribute('lengths'); + $orders = $index->getAttribute('orders'); + + $success = $dbForExternal->createIndex($collectionId, $id, $type, $attributes, $lengths, $orders); + if ($success) { + $dbForExternal->removeIndexInQueue($collectionId, $id); + } + } + /** * @param string $projectId * From 4567bc0d77dea6df3691e472a11493a5948e734e Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 18 Jun 2021 13:09:07 -0400 Subject: [PATCH 39/90] Ensure attributes are removed from queue before adding documents --- tests/e2e/Services/Database/DatabaseBase.php | 18 ++++++++++++++++++ .../Database/DatabaseCustomServerTest.php | 10 ++++++++++ 2 files changed, 28 insertions(+) diff --git a/tests/e2e/Services/Database/DatabaseBase.php b/tests/e2e/Services/Database/DatabaseBase.php index d75349b9d..5db307fff 100644 --- a/tests/e2e/Services/Database/DatabaseBase.php +++ b/tests/e2e/Services/Database/DatabaseBase.php @@ -88,6 +88,24 @@ trait DatabaseBase $this->assertEquals($actors['body']['required'], false); $this->assertEquals($actors['body']['array'], true); + // wait for database worker to create attributes + sleep(5); + + $movies = $this->client->call(Client::METHOD_GET, '/database/collections/' . $data['moviesId'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), []); + + $this->assertEquals($movies['body']['$id'], $title['body']['$collection']); + $this->assertEquals($movies['body']['$id'], $releaseYear['body']['$collection']); + $this->assertEquals($movies['body']['$id'], $actors['body']['$collection']); + $this->assertIsArray($movies['body']['attributes']); + $this->assertCount(3, $movies['body']['attributes']); + $this->assertEquals($movies['body']['attributes'][0]['$id'], $title['body']['$id']); + $this->assertEquals($movies['body']['attributes'][1]['$id'], $releaseYear['body']['$id']); + $this->assertEquals($movies['body']['attributes'][2]['$id'], $actors['body']['$id']); + return $data; } diff --git a/tests/e2e/Services/Database/DatabaseCustomServerTest.php b/tests/e2e/Services/Database/DatabaseCustomServerTest.php index 5c644c021..9be5bcbf3 100644 --- a/tests/e2e/Services/Database/DatabaseCustomServerTest.php +++ b/tests/e2e/Services/Database/DatabaseCustomServerTest.php @@ -55,12 +55,22 @@ class DatabaseCustomServerTest extends Scope 'required' => true, ]); + // wait for database worker to finish creating attributes + sleep(5); + $collection = $this->client->call(Client::METHOD_GET, '/database/collections/' . $actors['body']['$id'], array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), []); + $this->assertEquals($collection['body']['$id'], $firstName['body']['$collection']); + $this->assertEquals($collection['body']['$id'], $lastName['body']['$collection']); + $this->assertIsArray($collection['body']['attributes']); + $this->assertCount(2, $collection['body']['attributes']); + $this->assertEquals($collection['body']['attributes'][0]['$id'], $firstName['body']['$id']); + $this->assertEquals($collection['body']['attributes'][1]['$id'], $lastName['body']['$id']); + // Add Documents to the collection $document1 = $this->client->call(Client::METHOD_POST, '/database/collections/' . $actors['body']['$id'] . '/documents', array_merge([ 'content-type' => 'application/json', From 47d74a64be2eb3c15da12d54bad957ad8c5bdab1 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 18 Jun 2021 13:18:27 -0400 Subject: [PATCH 40/90] Add assertions to confirm index was created successfully --- app/workers/database.php | 6 +++--- tests/e2e/Services/Database/DatabaseBase.php | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/app/workers/database.php b/app/workers/database.php index 727f931a5..3c1919af5 100644 --- a/app/workers/database.php +++ b/app/workers/database.php @@ -39,9 +39,9 @@ class DatabaseV1 extends Worker $this->createAttribute($attribute, $projectId); break; case CREATE_TYPE_INDEX: - $attribute = $this->args['document'] ?? ''; - $attribute = new Document($attribute); - $this->createAttribute($attribute, $projectId); + $index = $this->args['document'] ?? ''; + $index = new Document($index); + $this->createIndex($index, $projectId); break; // case DELETE_TYPE_DOCUMENT: diff --git a/tests/e2e/Services/Database/DatabaseBase.php b/tests/e2e/Services/Database/DatabaseBase.php index 5db307fff..7dad8b14c 100644 --- a/tests/e2e/Services/Database/DatabaseBase.php +++ b/tests/e2e/Services/Database/DatabaseBase.php @@ -131,6 +131,20 @@ trait DatabaseBase $this->assertCount(1, $titleIndex['body']['attributes']); $this->assertEquals($titleIndex['body']['attributes'][0], 'title'); + // wait for database worker to create index + sleep(5); + + $movies = $this->client->call(Client::METHOD_GET, '/database/collections/' . $data['moviesId'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), []); + + $this->assertEquals($movies['body']['$id'], $titleIndex['body']['$collection']); + $this->assertIsArray($movies['body']['indexes']); + $this->assertCount(1, $movies['body']['indexes']); + $this->assertEquals($movies['body']['indexes'][0]['$id'], $titleIndex['body']['$id']); + return $data; } From b1a6302311a44c2ec468ea0441c12d68cba03cf2 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 18 Jun 2021 14:27:14 -0400 Subject: [PATCH 41/90] Use database worker to remove attributes and indexes --- app/controllers/api/database.php | 33 +++++++++++------------ app/workers/database.php | 46 +++++++++++++++++++++++++++++--- 2 files changed, 58 insertions(+), 21 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 44d02d720..d875952c8 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -250,6 +250,7 @@ App::post('/v1/database/collections/:collectionId/attributes') ->action(function ($collectionId, $id, $type, $size, $required, $signed, $array, $filters, $response, $dbForExternal, $database, $audits) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForExternal*/ + /** @var Appwrite\Event\Event $database */ /** @var Appwrite\Event\Event $audits */ $collection = $dbForExternal->getCollection($collectionId); @@ -382,12 +383,13 @@ App::delete('/v1/database/collections/:collectionId/attributes/:attributeId') ->param('attributeId', '', new Text(256), 'Attribute ID.') ->inject('response') ->inject('dbForExternal') + ->inject('database') ->inject('events') ->inject('audits') - ->inject('deletes') - ->action(function ($collectionId, $attributeId, $response, $dbForExternal, $events, $audits, $deletes) { + ->action(function ($collectionId, $attributeId, $response, $dbForExternal, $database, $events, $audits) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForExternal */ + /** @var Appwrite\Event\Event $database */ /** @var Appwrite\Event\Event $events */ /** @var Appwrite\Event\Event $audits */ @@ -410,13 +412,10 @@ App::delete('/v1/database/collections/:collectionId/attributes/:attributeId') 'collectionId' => $collectionId, ])]); - // TODO@kodumbeats use the deletes worker to handle this - $success = $dbForExternal->deleteAttribute($collectionId, $attributeId); - - // $deletes - // ->setParam('type', DELETE_TYPE_DOCUMENT) - // ->setParam('document', $attribute) - // ; + $database + ->setParam('type', DELETE_TYPE_ATTRIBUTE) + ->setParam('document', $attribute) + ; $events ->setParam('payload', $response->output2($attribute, Response::MODEL_ATTRIBUTE)) @@ -587,12 +586,13 @@ App::delete('/v1/database/collections/:collectionId/indexes/:indexId') ->param('indexId', '', new UID(), 'Index unique ID.') ->inject('response') ->inject('dbForExternal') + ->inject('database') ->inject('events') ->inject('audits') - ->inject('deletes') - ->action(function ($collectionId, $indexId, $response, $dbForExternal, $events, $audits, $deletes) { + ->action(function ($collectionId, $indexId, $response, $dbForExternal, $database, $events, $audits) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForExternal */ + /** @var Appwrite\Event\Event $database */ /** @var Appwrite\Event\Event $events */ /** @var Appwrite\Event\Event $audits */ @@ -615,13 +615,10 @@ App::delete('/v1/database/collections/:collectionId/indexes/:indexId') 'collectionId' => $collectionId, ])]); - // TODO@kodumbeats use the deletes worker to handle this - $success = $dbForExternal->deleteIndex($collectionId, $indexId); - - // $deletes - // ->setParam('type', DELETE_TYPE_DOCUMENT) - // ->setParam('document', $attribute) - // ; + $database + ->setParam('type', DELETE_TYPE_INDEX) + ->setParam('document', $index) + ; $events ->setParam('payload', $response->output2($index, Response::MODEL_INDEX)) diff --git a/app/workers/database.php b/app/workers/database.php index 3c1919af5..551df3837 100644 --- a/app/workers/database.php +++ b/app/workers/database.php @@ -38,11 +38,21 @@ class DatabaseV1 extends Worker $attribute = new Document($attribute); $this->createAttribute($attribute, $projectId); break; + case DELETE_TYPE_ATTRIBUTE: + $attribute = $this->args['document'] ?? ''; + $attribute = new Document($attribute); + $this->deleteAttribute($attribute, $projectId); + break; case CREATE_TYPE_INDEX: $index = $this->args['document'] ?? ''; $index = new Document($index); $this->createIndex($index, $projectId); break; + case DELETE_TYPE_INDEX: + $index = $this->args['document'] ?? ''; + $index = new Document($index); + $this->deleteIndex($index, $projectId); + break; // case DELETE_TYPE_DOCUMENT: // $document = $this->args['document'] ?? ''; @@ -97,8 +107,9 @@ class DatabaseV1 extends Worker /** * @param Document $attribute + * @param string $projectId */ - protected function createAttribute($attribute, $projectId) + protected function createAttribute($attribute, $projectId): void { $dbForExternal = $this->getExternalDB($projectId); @@ -118,9 +129,24 @@ class DatabaseV1 extends Worker } /** - * @param Document $index + * @param Document $attribute + * @param string $projectId */ - protected function createIndex($index, $projectId) + protected function deleteAttribute($attribute, $projectId): void + { + $dbForExternal = $this->getExternalDB($projectId); + + $collectionId = $attribute->getCollection(); + $id = $attribute->getAttribute('$id'); + + $success = $dbForExternal->deleteAttribute($collectionId, $id); + } + + /** + * @param Document $index + * @param string $projectId + */ + protected function createIndex($index, $projectId): void { $dbForExternal = $this->getExternalDB($projectId); @@ -137,6 +163,20 @@ class DatabaseV1 extends Worker } } + /** + * @param Document $index + * @param string $projectId + */ + protected function deleteIndex($index, $projectId): void + { + $dbForExternal = $this->getExternalDB($projectId); + + $collectionId = $index->getCollection(); + $id = $index->getAttribute('$id'); + + $success = $dbForExternal->deleteIndex($collectionId, $id); + } + /** * @param string $projectId * From 5bcb51ab5bfe5d097a85dba311bb9149f3ae38ae Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 18 Jun 2021 15:17:39 -0400 Subject: [PATCH 42/90] Clean up unused code --- app/workers/database.php | 46 ---------------------------------------- 1 file changed, 46 deletions(-) diff --git a/app/workers/database.php b/app/workers/database.php index 551df3837..1cf8c8c97 100644 --- a/app/workers/database.php +++ b/app/workers/database.php @@ -1,18 +1,12 @@ deleteIndex($index, $projectId); break; - // case DELETE_TYPE_DOCUMENT: - // $document = $this->args['document'] ?? ''; - // $document = new Document($document); - - // switch ($document->getCollection()) { - // case Database::SYSTEM_COLLECTION_PROJECTS: - // $this->deleteProject($document); - // break; - // case Database::SYSTEM_COLLECTION_FUNCTIONS: - // $this->deleteFunction($document, $projectId); - // break; - // case Database::SYSTEM_COLLECTION_USERS: - // $this->deleteUser($document, $projectId); - // break; - // case Database::SYSTEM_COLLECTION_COLLECTIONS: - // $this->deleteDocuments($document, $projectId); - // break; - // default: - // Console::error('No lazy delete operation available for document of type: '.$document->getCollection()); - // break; - // } - // break; - - // case DELETE_TYPE_EXECUTIONS: - // $this->deleteExecutionLogs($this->args['timestamp']); - // break; - - // case DELETE_TYPE_AUDIT: - // $this->deleteAuditLogs($this->args['timestamp']); - // break; - - // case DELETE_TYPE_ABUSE: - // $this->deleteAbuseLogs($this->args['timestamp']); - // break; - - // case DELETE_TYPE_CERTIFICATES: - // $document = new Document($this->args['document']); - // $this->deleteCertificates($document); - // break; - default: Console::error('No database operation for type: '.$type); break; From 9aad04b484f19168ccb07a84c81772d00df0de74 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 18 Jun 2021 15:27:03 -0400 Subject: [PATCH 43/90] Accept default permissions from Utopia for now --- app/controllers/api/database.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index d875952c8..9e528d167 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -49,8 +49,9 @@ App::post('/v1/database/collections') $write = (is_null($write)) ? ($collection->getWrite() ?? []) : $write; // By default inherit write permissions $collection->setAttribute('name', $name); - $collection->setAttribute('$read', $read); - $collection->setAttribute('$write', $write); + // TODO@kodumbeats Use the default permissions from Utopia for now + // $collection->setAttribute('$read', $read); + // $collection->setAttribute('$write', $write); $dbForExternal->updateDocument(Database::COLLECTIONS, $id, $collection); From 2e0b7fe471fe1c8021ca3e040907d56411df674c Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 18 Jun 2021 15:27:16 -0400 Subject: [PATCH 44/90] Support queries for listCollections --- app/controllers/api/database.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 9e528d167..f00689318 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -76,16 +76,20 @@ App::get('/v1/database/collections') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_COLLECTION_LIST) + ->param('queries', [], new ArrayList(new Text(128)), 'Array of query strings.', true) ->param('limit', 25, new Range(0, 100), 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) ->param('offset', 0, new Range(0, 40000), 'Results offset. The default value is 0. Use this param to manage pagination.', true) ->inject('response') ->inject('dbForExternal') - ->action(function ($limit, $offset, $response, $dbForExternal) { + ->action(function ($queries, $limit, $offset, $response, $dbForExternal) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForExternal */ - $collections = $dbForExternal->listCollections($limit, $offset); - // TODO@kodumbeats allow for filtering collections + $queries = \array_map(function ($query) { + return Query::parse($query); + }, $queries); + + $collections = $dbForExternal->find(Database::COLLECTIONS, $queries, $limit, $offset); $response->dynamic2(new Document([ 'collections' => $collections, From ea46b7b783c44fb19b851a4324b777701e4daef4 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Tue, 22 Jun 2021 15:34:42 -0400 Subject: [PATCH 45/90] Use stricter validators for params --- app/controllers/api/database.php | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index f00689318..34712d576 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -9,6 +9,7 @@ use Utopia\Validator\WhiteList; use Utopia\Validator\Text; use Utopia\Validator\ArrayList; use Utopia\Validator\JSON; +use Utopia\Database\Validator\Key; use Appwrite\Database\Validator\UID; use Utopia\Database\Exception\Authorization as AuthorizationException; use Utopia\Database\Exception\Structure as StructureException; @@ -241,13 +242,13 @@ App::post('/v1/database/collections/:collectionId/attributes') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_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('id', '', new Text(256), 'Attribute ID.') + ->param('id', '', new Key(), 'Attribute ID.') ->param('type', null, new Text(256), 'Attribute type.') ->param('size', null, new Numeric(), 'Attribute size for text attributes, in number of characters. For integers, floats, or bools, use 0.') ->param('required', null, new Boolean(), 'Is attribute required?') ->param('signed', true, new Boolean(), 'Is attribute signed?', true) ->param('array', false, new Boolean(), 'Is attribute an array?', true) - ->param('filters', [], new ArrayList(new Whitelist(['encrypt', 'json'])), 'Array of filters.', true) + // ->param('filters', [], new ArrayList(new Whitelist(['encrypt', 'json'])), 'Array of filters.', true) ->inject('response') ->inject('dbForExternal') ->inject('database') @@ -344,7 +345,7 @@ App::get('v1/database/collections/:collectionId/attributes/:attributeId') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_ATTRIBUTE) ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') - ->param('attributeId', '', new Text(256), 'Attribute ID.') + ->param('attributeId', '', new Key(), 'Attribute ID.') ->inject('response') ->inject('dbForExternal') ->action(function ($collectionId, $attributeId, $response, $dbForExternal) { @@ -385,7 +386,7 @@ App::delete('/v1/database/collections/:collectionId/attributes/:attributeId') ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.model', Response::MODEL_NONE) ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') - ->param('attributeId', '', new Text(256), 'Attribute ID.') + ->param('attributeId', '', new Key(), 'Attribute ID.') ->inject('response') ->inject('dbForExternal') ->inject('database') @@ -448,11 +449,11 @@ App::post('/v1/database/collections/:collectionId/indexes') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_INDEX) ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') - ->param('id', null, new Text(256), 'Index ID.') + ->param('id', null, new Key(), 'Index ID.') ->param('type', null, new WhiteList([Database::INDEX_KEY, Database::INDEX_FULLTEXT, Database::INDEX_UNIQUE, Database::INDEX_SPATIAL, Database::INDEX_ARRAY]), 'Index type.') - ->param('attributes', null, new ArrayList(new Text(256)), 'Array of attributes to index.') - ->param('lengths', [], new ArrayList(new Text(256)), 'Array of index lengths.', true) - ->param('orders', [], new ArrayList(new Text(256)), 'Array of index orders.', true) + ->param('attributes', null, new ArrayList(new Key()), 'Array of attributes to index.') + // ->param('lengths', [], new ArrayList(new Text(256)), 'Array of index lengths.', true) + ->param('orders', [], new ArrayList(new WhiteList(['ASC', 'DESC'])), 'Array of index orders.', true) ->inject('response') ->inject('dbForExternal') ->inject('database') @@ -547,7 +548,7 @@ App::get('v1/database/collections/:collectionId/indexes/:indexId') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_INDEX) ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') - ->param('indexId', null, new Text(256), 'Index ID.') + ->param('indexId', null, new Key(), 'Index ID.') ->inject('response') ->inject('dbForExternal') ->action(function ($collectionId, $indexId, $response, $dbForExternal) { @@ -588,7 +589,7 @@ App::delete('/v1/database/collections/:collectionId/indexes/:indexId') ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.model', Response::MODEL_NONE) ->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') - ->param('indexId', '', new UID(), 'Index unique ID.') + ->param('indexId', '', new Key(), 'Index ID.') ->inject('response') ->inject('dbForExternal') ->inject('database') From 4c81d6b4907b2cf8fc0b574dd97c3171af9bffdf Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Wed, 23 Jun 2021 10:21:32 -0400 Subject: [PATCH 46/90] Remove unnecessary params from new attribute and index routes --- app/controllers/api/database.php | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 34712d576..638e58232 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -246,14 +246,12 @@ App::post('/v1/database/collections/:collectionId/attributes') ->param('type', null, new Text(256), 'Attribute type.') ->param('size', null, new Numeric(), 'Attribute size for text attributes, in number of characters. For integers, floats, or bools, use 0.') ->param('required', null, new Boolean(), 'Is attribute required?') - ->param('signed', true, new Boolean(), 'Is attribute signed?', true) ->param('array', false, new Boolean(), 'Is attribute an array?', true) - // ->param('filters', [], new ArrayList(new Whitelist(['encrypt', 'json'])), 'Array of filters.', true) ->inject('response') ->inject('dbForExternal') ->inject('database') ->inject('audits') - ->action(function ($collectionId, $id, $type, $size, $required, $signed, $array, $filters, $response, $dbForExternal, $database, $audits) { + ->action(function ($collectionId, $id, $type, $size, $required, $array, $response, $dbForExternal, $database, $audits) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForExternal*/ /** @var Appwrite\Event\Event $database */ @@ -265,10 +263,16 @@ App::post('/v1/database/collections/:collectionId/attributes') throw new Exception('Collection not found', 404); } + // integers are signed by default, and filters are hidden from the endpoint. + $signed = true; + $filters = []; + $success = $dbForExternal->addAttributeInQueue($collectionId, $id, $type, $size, $required, $signed, $array, $filters); // Database->addAttributeInQueue() does not return a document // So we need to create one for the response + // + // TODO@kodumbeats should $signed and $filters be part of the response model? $attribute = new Document([ '$collection' => $collectionId, '$id' => $id, @@ -323,7 +327,7 @@ App::get('v1/database/collections/:collectionId/attributes') $attributes = array_map(function ($attribute) use ($collection) { return new Document([\array_merge($attribute, [ - 'collectionId' => $collection->getId(), + 'collectionId' => $collection->getId(), ])]); }, $attributes); @@ -452,13 +456,12 @@ App::post('/v1/database/collections/:collectionId/indexes') ->param('id', null, new Key(), 'Index ID.') ->param('type', null, new WhiteList([Database::INDEX_KEY, Database::INDEX_FULLTEXT, Database::INDEX_UNIQUE, Database::INDEX_SPATIAL, Database::INDEX_ARRAY]), 'Index type.') ->param('attributes', null, new ArrayList(new Key()), 'Array of attributes to index.') - // ->param('lengths', [], new ArrayList(new Text(256)), 'Array of index lengths.', true) ->param('orders', [], new ArrayList(new WhiteList(['ASC', 'DESC'])), 'Array of index orders.', true) ->inject('response') ->inject('dbForExternal') ->inject('database') ->inject('audits') - ->action(function ($collectionId, $id, $type, $attributes, $lengths, $orders, $response, $dbForExternal, $database, $audits) { + ->action(function ($collectionId, $id, $type, $attributes, $orders, $response, $dbForExternal, $database, $audits) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForExternal */ /** @var Appwrite\Event\Event $audits */ @@ -469,10 +472,15 @@ App::post('/v1/database/collections/:collectionId/indexes') throw new Exception('Collection not found', 404); } + // lengths hidden by default + $lengths = []; + $success = $dbForExternal->addIndexInQueue($collectionId, $id, $type, $attributes, $lengths, $orders); // Database->createIndex() does not return a document // So we need to create one for the response + // + // TODO@kodumbeats should $lengths be a part of the response model? $index = new Document([ '$collection' => $collectionId, '$id' => $id, @@ -526,7 +534,7 @@ App::get('v1/database/collections/:collectionId/indexes') $indexes = array_map(function ($index) use ($collection) { return new Document([\array_merge($index, [ - 'collectionId' => $collection->getId(), + 'collectionId' => $collection->getId(), ])]); }, $indexes); From 12b58b00acfa7add0d8963adf4b22a001b4d1f99 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Wed, 30 Jun 2021 11:10:45 -0400 Subject: [PATCH 47/90] Update descriptions/examples for docs Applied suggestions from code review Co-authored-by: Eldad A. Fux --- app/controllers/api/database.php | 20 +++++++++---------- .../Utopia/Response/Model/Collection.php | 8 ++++---- .../Utopia/Response/Model/Document.php | 4 ++-- src/Appwrite/Utopia/Response/Model/Index.php | 4 ++-- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 638e58232..f9bbaeee6 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -310,7 +310,7 @@ App::get('v1/database/collections/:collectionId/attributes') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_ATTRIBUTE_LIST) - ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') + ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection using the Database service [server integration](/docs/server/database#createCollection).') ->inject('response') ->inject('dbForExternal') ->action(function ($collectionId, $response, $dbForExternal) { @@ -348,7 +348,7 @@ App::get('v1/database/collections/:collectionId/attributes/:attributeId') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_ATTRIBUTE) - ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') + ->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') ->inject('dbForExternal') @@ -389,7 +389,7 @@ App::delete('/v1/database/collections/:collectionId/attributes/:attributeId') ->label('sdk.description', '/docs/references/database/delete-attribute.md') ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.model', Response::MODEL_NONE) - ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') + ->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') ->inject('dbForExternal') @@ -452,7 +452,7 @@ App::post('/v1/database/collections/:collectionId/indexes') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_INDEX) - ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') + ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection using the Database service [server integration](/docs/server/database#createCollection).') ->param('id', null, new Key(), 'Index ID.') ->param('type', null, new WhiteList([Database::INDEX_KEY, Database::INDEX_FULLTEXT, Database::INDEX_UNIQUE, Database::INDEX_SPATIAL, Database::INDEX_ARRAY]), 'Index type.') ->param('attributes', null, new ArrayList(new Key()), 'Array of attributes to index.') @@ -517,7 +517,7 @@ App::get('v1/database/collections/:collectionId/indexes') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_INDEX_LIST) - ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') + ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection using the Database service [server integration](/docs/server/database#createCollection).') ->inject('response') ->inject('dbForExternal') ->action(function ($collectionId, $response, $dbForExternal) { @@ -555,7 +555,7 @@ App::get('v1/database/collections/:collectionId/indexes/:indexId') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_INDEX) - ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') + ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection using the Database service [server integration](/docs/server/database#createCollection).') ->param('indexId', null, new Key(), 'Index ID.') ->inject('response') ->inject('dbForExternal') @@ -596,7 +596,7 @@ App::delete('/v1/database/collections/:collectionId/indexes/:indexId') ->label('sdk.description', '/docs/references/database/delete-index.md') ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.model', Response::MODEL_NONE) - ->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') + ->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection using the Database service [server integration](/docs/server/database#createCollection).') ->param('indexId', '', new Key(), 'Index ID.') ->inject('response') ->inject('dbForExternal') @@ -742,7 +742,7 @@ App::get('/v1/database/collections/:collectionId/documents') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_DOCUMENT_LIST) - ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') + ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection using the Database service [server integration](/docs/server/database#createCollection).') ->param('queries', [], new ArrayList(new Text(128)), 'Array of query strings.', true) ->param('limit', 25, new Range(0, 100), 'Maximum number of documents to return in response. Use this value to manage pagination. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) ->param('offset', 0, new Range(0, 900000000), 'Offset value. The default value is 0. Use this param to manage pagination.', true) @@ -783,7 +783,7 @@ App::get('/v1/database/collections/:collectionId/documents/:documentId') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_DOCUMENT) - ->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') + ->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection using the Database service [server integration](/docs/server/database#createCollection).') ->param('documentId', null, new UID(), 'Document unique ID.') ->inject('response') ->inject('dbForExternal') @@ -888,7 +888,7 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId') ->label('sdk.description', '/docs/references/database/delete-document.md') ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.model', Response::MODEL_NONE) - ->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') + ->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection using the Database service [server integration](/docs/server/database#createCollection).') ->param('documentId', null, new UID(), 'Document unique ID.') ->inject('response') ->inject('dbForExternal') diff --git a/src/Appwrite/Utopia/Response/Model/Collection.php b/src/Appwrite/Utopia/Response/Model/Collection.php index a3659e027..4c1db0f53 100644 --- a/src/Appwrite/Utopia/Response/Model/Collection.php +++ b/src/Appwrite/Utopia/Response/Model/Collection.php @@ -15,20 +15,20 @@ class Collection extends Model 'type' => self::TYPE_STRING, 'description' => 'Collection ID.', 'default' => '', - 'example' => '', + 'example' => '5e5ea5c16897e', ]) ->addRule('$read', [ 'type' => self::TYPE_STRING, 'description' => 'Collection read permissions.', 'default' => '', - 'example' => '', + 'example' => 'role:all', 'array' => true ]) ->addRule('$write', [ 'type' => self::TYPE_STRING, 'description' => 'Collection write permissions.', 'default' => '', - 'example' => '', + 'example' => 'user:608f9da25e7e1', 'array' => true ]) ->addRule('name', [ @@ -87,4 +87,4 @@ class Collection extends Model { return Response::MODEL_COLLECTION; } -} \ No newline at end of file +} diff --git a/src/Appwrite/Utopia/Response/Model/Document.php b/src/Appwrite/Utopia/Response/Model/Document.php index ba9092137..2ee8e8cf4 100644 --- a/src/Appwrite/Utopia/Response/Model/Document.php +++ b/src/Appwrite/Utopia/Response/Model/Document.php @@ -45,14 +45,14 @@ class Document extends Any 'type' => self::TYPE_STRING, 'description' => 'Document read permissions.', 'default' => '', - 'example' => '', + 'example' => 'role:all', 'array' => true, ]) ->addRule('$write', [ 'type' => self::TYPE_STRING, 'description' => 'Document write permissions.', 'default' => '', - 'example' => '', + 'example' => 'user:608f9da25e7e1', 'array' => true, ]) ; diff --git a/src/Appwrite/Utopia/Response/Model/Index.php b/src/Appwrite/Utopia/Response/Model/Index.php index f67637a26..3ae1536aa 100644 --- a/src/Appwrite/Utopia/Response/Model/Index.php +++ b/src/Appwrite/Utopia/Response/Model/Index.php @@ -14,7 +14,7 @@ class Index extends Model 'type' => self::TYPE_STRING, 'description' => 'Collection ID.', 'default' => '', - 'example' => '', + 'example' => '5e5ea5c16d55', ]) ->addRule('$id', [ 'type' => self::TYPE_STRING, @@ -73,4 +73,4 @@ class Index extends Model { return Response::MODEL_INDEX; } -} \ No newline at end of file +} From 6885d51e821893b5869dd7d7fa5d0d3226be3b9b Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Wed, 30 Jun 2021 14:33:05 -0400 Subject: [PATCH 48/90] Remove unneeded $member roles --- app/config/roles.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/config/roles.php b/app/config/roles.php index 1d1e761f0..34af1461f 100644 --- a/app/config/roles.php +++ b/app/config/roles.php @@ -11,10 +11,6 @@ $member = [ 'teams.write', 'documents.read', 'documents.write', - 'attributes.read', - 'attributes.write', - 'indexes.read', - 'indexes.write', 'files.read', 'files.write', 'projects.read', From 1454dec6adf6204ead463858969167884f49f543 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Wed, 30 Jun 2021 14:33:25 -0400 Subject: [PATCH 49/90] Define defaults for getAttribute --- app/workers/database.php | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/app/workers/database.php b/app/workers/database.php index 1cf8c8c97..8b93bcfd4 100644 --- a/app/workers/database.php +++ b/app/workers/database.php @@ -68,13 +68,13 @@ class DatabaseV1 extends Worker $dbForExternal = $this->getExternalDB($projectId); $collectionId = $attribute->getCollection(); - $id = $attribute->getAttribute('$id'); - $type = $attribute->getAttribute('type'); - $size = $attribute->getAttribute('size'); - $required = $attribute->getAttribute('required'); - $signed = $attribute->getAttribute('signed'); - $array = $attribute->getAttribute('array'); - $filters = $attribute->getAttribute('filters'); + $id = $attribute->getAttribute('$id', ''); + $type = $attribute->getAttribute('type', ''); + $size = $attribute->getAttribute('size', 0); + $required = $attribute->getAttribute('required', false); + $signed = $attribute->getAttribute('signed', true); + $array = $attribute->getAttribute('array', false); + $filters = $attribute->getAttribute('filters', []); $success = $dbForExternal->createAttribute($collectionId, $id, $type, $size, $required, $signed, $array, $filters); if ($success) { @@ -105,11 +105,11 @@ class DatabaseV1 extends Worker $dbForExternal = $this->getExternalDB($projectId); $collectionId = $index->getCollection(); - $id = $index->getAttribute('$id'); - $type = $index->getAttribute('type'); - $attributes = $index->getAttribute('attributes'); - $lengths = $index->getAttribute('lengths'); - $orders = $index->getAttribute('orders'); + $id = $index->getAttribute('$id', ''); + $type = $index->getAttribute('type', ''); + $attributes = $index->getAttribute('attributes', []); + $lengths = $index->getAttribute('lengths', []); + $orders = $index->getAttribute('orders', []); $success = $dbForExternal->createIndex($collectionId, $id, $type, $attributes, $lengths, $orders); if ($success) { From 48e15c7916054b56aad0e40da0cc109f16a9fa06 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Wed, 30 Jun 2021 14:35:32 -0400 Subject: [PATCH 50/90] Remove unneeded volume mounts from database worker --- docker-compose.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 8fc1bff54..54998b264 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -248,10 +248,6 @@ services: networks: - appwrite volumes: - - appwrite-uploads:/storage/uploads:rw - - appwrite-cache:/storage/cache:rw - - appwrite-functions:/storage/functions:rw - - appwrite-certificates:/storage/certificates:rw - ./app:/usr/src/code/app - ./src:/usr/src/code/src depends_on: From 1ae6cf39f61f0de8483b9cba157c50e2ee20eebc Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Wed, 30 Jun 2021 14:49:54 -0400 Subject: [PATCH 51/90] Add examples to response model for docs --- src/Appwrite/Utopia/Response/Model/Attribute.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Appwrite/Utopia/Response/Model/Attribute.php b/src/Appwrite/Utopia/Response/Model/Attribute.php index fc48d7c1e..1dceb4999 100644 --- a/src/Appwrite/Utopia/Response/Model/Attribute.php +++ b/src/Appwrite/Utopia/Response/Model/Attribute.php @@ -14,19 +14,19 @@ class Attribute extends Model 'type' => self::TYPE_STRING, 'description' => 'Collection ID.', 'default' => '', - 'example' => '', + 'example' => '60ccf71b98a2d', ]) ->addRule('$id', [ 'type' => self::TYPE_STRING, 'description' => 'Attribute ID.', 'default' => '', - 'example' => '', + 'example' => '60ccf71b98a2d', ]) ->addRule('type', [ 'type' => self::TYPE_STRING, 'description' => 'Attribute type.', 'default' => '', - 'example' => '', + 'example' => 'integer', ]) ->addRule('size', [ 'type' => self::TYPE_STRING, From 0d895c54869951c3649c5d3eb5d80926aa7f8724 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 1 Jul 2021 12:24:13 -0400 Subject: [PATCH 52/90] Upgrade to utopia-php/database:0.4.0 --- composer.json | 6 +-- composer.lock | 136 ++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 101 insertions(+), 41 deletions(-) diff --git a/composer.json b/composer.json index f0f3d973d..609c073f5 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": "0.3.*", + "utopia-php/database": "0.4.*", "utopia-php/locale": "0.3.*", "utopia-php/registry": "0.5.*", "utopia-php/preloader": "0.2.*", @@ -65,11 +65,11 @@ "repositories": [ { "type": "git", - "url": "https://github.com/lohanidamodar/audit" + "url": "https://github.com/kodumbeats/audit" }, { "type": "git", - "url": "https://github.com/lohanidamodar/abuse" + "url": "https://github.com/kodumbeats/abuse" } ], "require-dev": { diff --git a/composer.lock b/composer.lock index 162af579f..78bdfbcb2 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": "34850a190b81047143e4fd6466e84a53", + "content-hash": "3ac373911997865bce95b62656cd0af0", "packages": [ { "name": "adhocore/jwt", @@ -647,29 +647,32 @@ }, { "name": "guzzlehttp/psr7", - "version": "1.8.2", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "dc960a912984efb74d0a90222870c72c87f10c91" + "reference": "1dc8d9cba3897165e16d12bb13d813afb1eb3fe7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/dc960a912984efb74d0a90222870c72c87f10c91", - "reference": "dc960a912984efb74d0a90222870c72c87f10c91", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/1dc8d9cba3897165e16d12bb13d813afb1eb3fe7", + "reference": "1dc8d9cba3897165e16d12bb13d813afb1eb3fe7", "shasum": "" }, "require": { - "php": ">=5.4.0", - "psr/http-message": "~1.0", - "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "ralouphie/getallheaders": "^3.0" }, "provide": { + "psr/http-factory-implementation": "1.0", "psr/http-message-implementation": "1.0" }, "require-dev": { - "ext-zlib": "*", - "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10" + "bamarni/composer-bin-plugin": "^1.4.1", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.8 || ^9.3.10" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -677,16 +680,13 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7-dev" + "dev-master": "2.0-dev" } }, "autoload": { "psr-4": { "GuzzleHttp\\Psr7\\": "src/" - }, - "files": [ - "src/functions_include.php" - ] + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -701,6 +701,11 @@ { "name": "Tobias Schultze", "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" } ], "description": "PSR-7 message implementation that also provides common utility methods", @@ -716,9 +721,9 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/1.8.2" + "source": "https://github.com/guzzle/psr7/tree/2.0.0" }, - "time": "2021-04-26T09:17:50+00:00" + "time": "2021-06-30T20:03:07+00:00" }, { "name": "influxdb/influxdb-php", @@ -1156,6 +1161,61 @@ }, "time": "2020-06-29T06:28:15+00:00" }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, { "name": "psr/http-message", "version": "1.0.1", @@ -1606,13 +1666,13 @@ "version": "dev-feat-utopia-db-integration", "source": { "type": "git", - "url": "https://github.com/lohanidamodar/abuse", - "reference": "8486a4f95248e5a9fb1fe9b650278b264857e929" + "url": "https://github.com/kodumbeats/abuse", + "reference": "5e78c15e3bd1afcbb5cca417916294b4e63e7e5f" }, "require": { "ext-pdo": "*", "php": ">=7.4", - "utopia-php/database": "0.3.*" + "utopia-php/database": "0.4.*" }, "require-dev": { "phpunit/phpunit": "^9.4", @@ -1641,7 +1701,7 @@ "upf", "utopia" ], - "time": "2021-06-18T06:21:55+00:00" + "time": "2021-07-01T14:20:57+00:00" }, { "name": "utopia-php/analytics", @@ -1703,13 +1763,13 @@ "version": "dev-feat-utopia-db-integration", "source": { "type": "git", - "url": "https://github.com/lohanidamodar/audit", - "reference": "063c1d527776c85d0ab6d8ffcde694f7813170a6" + "url": "https://github.com/kodumbeats/audit", + "reference": "25535352205f717c2320cd375242e5963677e344" }, "require": { "ext-pdo": "*", "php": ">=7.4", - "utopia-php/database": "0.3.*" + "utopia-php/database": "0.4.*" }, "require-dev": { "phpunit/phpunit": "^9.3", @@ -1738,7 +1798,7 @@ "upf", "utopia" ], - "time": "2021-06-18T06:19:19+00:00" + "time": "2021-07-01T14:07:43+00:00" }, { "name": "utopia-php/cache", @@ -1899,16 +1959,16 @@ }, { "name": "utopia-php/database", - "version": "0.3.2", + "version": "0.4.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "8e56a2d399d17b2497c8ee0f8bf429ac2da90c56" + "reference": "0b59d09a6622b472f82013623fd44e2b1b5504db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/8e56a2d399d17b2497c8ee0f8bf429ac2da90c56", - "reference": "8e56a2d399d17b2497c8ee0f8bf429ac2da90c56", + "url": "https://api.github.com/repos/utopia-php/database/zipball/0b59d09a6622b472f82013623fd44e2b1b5504db", + "reference": "0b59d09a6622b472f82013623fd44e2b1b5504db", "shasum": "" }, "require": { @@ -1956,9 +2016,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.3.2" + "source": "https://github.com/utopia-php/database/tree/0.4.0" }, - "time": "2021-06-12T18:26:26+00:00" + "time": "2021-06-29T15:52:49+00:00" }, { "name": "utopia-php/domains", @@ -5802,16 +5862,16 @@ }, { "name": "symfony/string", - "version": "v5.3.2", + "version": "v5.3.3", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "0732e97e41c0a590f77e231afc16a327375d50b0" + "reference": "bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/0732e97e41c0a590f77e231afc16a327375d50b0", - "reference": "0732e97e41c0a590f77e231afc16a327375d50b0", + "url": "https://api.github.com/repos/symfony/string/zipball/bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1", + "reference": "bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1", "shasum": "" }, "require": { @@ -5865,7 +5925,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.3.2" + "source": "https://github.com/symfony/string/tree/v5.3.3" }, "funding": [ { @@ -5881,7 +5941,7 @@ "type": "tidelift" } ], - "time": "2021-06-06T09:51:56+00:00" + "time": "2021-06-27T11:44:38+00:00" }, { "name": "theseer/tokenizer", @@ -6195,5 +6255,5 @@ "platform-overrides": { "php": "8.0" }, - "plugin-api-version": "2.0.0" + "plugin-api-version": "2.1.0" } From 536f953f1df2e5e92b7e6ebb31d385cf9ebbef22 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 1 Jul 2021 13:48:37 -0400 Subject: [PATCH 53/90] Retrieve cache and db without registry --- app/init.php | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/app/init.php b/app/init.php index c01c9dddf..e3aede7ca 100644 --- a/app/init.php +++ b/app/init.php @@ -600,32 +600,26 @@ App::setResource('projectDB', function($db, $cache, $project) { return $projectDB; }, ['db', 'cache', 'project']); -App::setResource('dbForInternal', function($register, $project) { - $cache = new Cache(new RedisCache($register->get('cache'))); +App::setResource('dbForInternal', function($db, $cache, $project) { + $dbForInternal = new Database2(new MariaDB($db), $cache); + $dbForInternal->setNamespace('project_'.$project->getId().'_internal'); - $database = new Database2(new MariaDB($register->get('db')), $cache); - $database->setNamespace('project_'.$project->getId().'_internal'); + return $dbForInternal; +}, ['db', 'cache', 'project']); - return $database; -}, ['register', 'project']); +App::setResource('dbForExternal', function($db, $cache, $project) { + $dbForExternal = new Database2(new MariaDB($db), $cache); + $dbForExternal->setNamespace('project_'.$project->getId().'_external'); -App::setResource('dbForExternal', function($register, $project) { - $cache = new Cache(new RedisCache($register->get('cache'))); + return $dbForExternal; +}, ['db', 'cache', 'project']); - $database = new Database2(new MariaDB($register->get('db')), $cache); - $database->setNamespace('project_'.$project->getId().'_external'); +App::setResource('dbForConsole', function($db, $cache) { + $dbForConsole = new Database2(new MariaDB($db), $cache); + $dbForConsole->setNamespace('project_console_internal'); - return $database; -}, ['register', 'project']); - -App::setResource('dbForConsole', function($register) { - $cache = new Cache(new RedisCache($register->get('cache'))); - - $database = new Database2(new MariaDB($register->get('db')), $cache); - $database->setNamespace('project_console_internal'); - - return $database; -}, ['register']); + return $dbForConsole; +}, ['db', 'cache']); App::setResource('mode', function($request) { /** @var Utopia\Swoole\Request $request */ From d7cdb254469d814a8fbd71633c78a2302ad606fc Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 1 Jul 2021 16:34:21 -0400 Subject: [PATCH 54/90] Fix database constructors --- app/init.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/init.php b/app/init.php index e3aede7ca..1b0a0f944 100644 --- a/app/init.php +++ b/app/init.php @@ -601,21 +601,21 @@ App::setResource('projectDB', function($db, $cache, $project) { }, ['db', 'cache', 'project']); App::setResource('dbForInternal', function($db, $cache, $project) { - $dbForInternal = new Database2(new MariaDB($db), $cache); + $dbForInternal = new Database2(new MariaDB($db), new Cache(new RedisCache($cache))); $dbForInternal->setNamespace('project_'.$project->getId().'_internal'); return $dbForInternal; }, ['db', 'cache', 'project']); App::setResource('dbForExternal', function($db, $cache, $project) { - $dbForExternal = new Database2(new MariaDB($db), $cache); + $dbForExternal = new Database2(new MariaDB($db), new Cache(new RedisCache($cache))); $dbForExternal->setNamespace('project_'.$project->getId().'_external'); return $dbForExternal; }, ['db', 'cache', 'project']); App::setResource('dbForConsole', function($db, $cache) { - $dbForConsole = new Database2(new MariaDB($db), $cache); + $dbForConsole = new Database2(new MariaDB($db), new Cache(new RedisCache($cache))); $dbForConsole->setNamespace('project_console_internal'); return $dbForConsole; From 0b9f6341d202c971d7f5406497cce126cbb95bff Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 1 Jul 2021 16:35:09 -0400 Subject: [PATCH 55/90] Get a connection from pool in coroutine --- app/http.php | 124 +++++++++++++++++++++++++++++---------------------- 1 file changed, 71 insertions(+), 53 deletions(-) diff --git a/app/http.php b/app/http.php index e35a83724..69dbbd98b 100644 --- a/app/http.php +++ b/app/http.php @@ -52,62 +52,80 @@ include __DIR__ . '/controllers/general.php'; $http->on('start', function (Server $http) use ($payloadSize, $register) { $app = new App('UTC'); - // Only retry connection once before throwing exception - try { - $dbForConsole = $app->getResource('dbForConsole'); /** @var Utopia\Database\Database $dbForConsole */ - } catch (\Exception $exception) { - Console::warning('[Setup] - Database not ready. Waiting for five seconds...'); - sleep(5); - $dbForConsole = $app->getResource('dbForConsole'); /** @var Utopia\Database\Database $dbForConsole */ - } + go(function() use ($register, $app) { - if(!$dbForConsole->exists()) { - Console::success('[Setup] - Server database init started...'); - - $collections = Config::getParam('collections2', []); /** @var array $collections */ - - $register->get('cache')->flushAll(); - - $dbForConsole->create(); - - $audit = new Audit($dbForConsole); - $audit->setup(); - - $adapter = new TimeLimit("", 0, 1, $dbForConsole); - $adapter->setup(); - - foreach ($collections as $key => $collection) { - Console::success('[Setup] - Creating collection: ' . $collection['$id'] . '...'); - - $dbForConsole->createCollection($key); - - foreach ($collection['attributes'] as $i => $attribute) { - $dbForConsole->createAttribute( - $key, - $attribute['$id'], - $attribute['type'], - $attribute['size'], - $attribute['required'], - $attribute['signed'], - $attribute['array'], - $attribute['filters'], - ); - } - - foreach ($collection['indexes'] as $i => $index) { - $dbForConsole->createIndex( - $key, - $index['$id'], - $index['type'], - $index['attributes'], - $index['lengths'], - $index['orders'], - ); - } + // Only retry connection once before throwing exception + try { + $db = $register->get('dbPool')->get(); + } catch (\Exception $exception) { + Console::warning('[Setup] - Database not ready. Waiting for five seconds...'); + sleep(5); } + $db = $register->get('dbPool')->get(); + $redis = $register->get('redisPool')->get(); - Console::success('[Setup] - Server database init completed...'); - } + App::setResource('db', function () use (&$db) { + return $db; + }); + + App::setResource('cache', function () use (&$redis) { + return $redis; + }); + + App::setResource('app', function() use (&$app) { + return $app; + }); + + $dbForConsole = $app->getResource('dbForConsole'); /** @var Utopia\Database\Database $dbForConsole */ + + if(!$dbForConsole->exists()) { + Console::success('[Setup] - Server database init started...'); + + $collections = Config::getParam('collections2', []); /** @var array $collections */ + + $redis->flushAll(); + + $dbForConsole->create(); + + $audit = new \Utopia\Audit\Audit($dbForConsole); + $audit->setup(); + + $adapter = new TimeLimit("", 0, 1, $dbForConsole); + $adapter->setup(); + + foreach ($collections as $key => $collection) { + Console::success('[Setup] - Creating collection: ' . $collection['$id'] . '...'); + + $dbForConsole->createCollection($key); + + foreach ($collection['attributes'] as $i => $attribute) { + $dbForConsole->createAttribute( + $key, + $attribute['$id'], + $attribute['type'], + $attribute['size'], + $attribute['required'], + $attribute['signed'], + $attribute['array'], + $attribute['filters'], + ); + } + + foreach ($collection['indexes'] as $i => $index) { + $dbForConsole->createIndex( + $key, + $index['$id'], + $index['type'], + $index['attributes'], + $index['lengths'], + $index['orders'], + ); + } + } + + Console::success('[Setup] - Server database init completed...'); + } + }); Console::success('Server started succefully (max payload is '.number_format($payloadSize).' bytes)'); From 017ee134e6f1d26a97d88ddb798ec08d52a8c501 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 2 Jul 2021 11:46:57 -0400 Subject: [PATCH 56/90] Use dev branches until features are merged --- composer.json | 10 +++++++--- composer.lock | 45 ++++++++++++++++++++------------------------- 2 files changed, 27 insertions(+), 28 deletions(-) diff --git a/composer.json b/composer.json index 4905b72a5..3d0b7e7bb 100644 --- a/composer.json +++ b/composer.json @@ -39,13 +39,13 @@ "appwrite/php-runtimes": "0.3.*", "utopia-php/framework": "0.14.*", - "utopia-php/abuse": "dev-feat-utopia-db-integration", + "utopia-php/abuse": "dev-dev", "utopia-php/analytics": "0.2.*", - "utopia-php/audit": "dev-feat-utopia-db-integration", + "utopia-php/audit": "dev-dev", "utopia-php/cache": "0.4.*", "utopia-php/cli": "0.11.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "0.4.*", + "utopia-php/database": "dev-dev", "utopia-php/locale": "0.3.*", "utopia-php/registry": "0.5.*", "utopia-php/preloader": "0.2.*", @@ -70,6 +70,10 @@ { "type": "git", "url": "https://github.com/kodumbeats/abuse" + }, + { + "type": "git", + "url": "https://github.com/kodumbeats/database" } ], "require-dev": { diff --git a/composer.lock b/composer.lock index a7a5c17cf..0855b9449 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": "3ac373911997865bce95b62656cd0af0", + "content-hash": "58b51079b2abae7c761ecb0f1cfa774c", "packages": [ { "name": "adhocore/jwt", @@ -1666,16 +1666,16 @@ }, { "name": "utopia-php/abuse", - "version": "dev-feat-utopia-db-integration", + "version": "dev-dev", "source": { "type": "git", "url": "https://github.com/kodumbeats/abuse", - "reference": "5e78c15e3bd1afcbb5cca417916294b4e63e7e5f" + "reference": "5d4e54263d1ae828b2a79ccb8c11aa9881451e82" }, "require": { "ext-pdo": "*", "php": ">=7.4", - "utopia-php/database": "0.4.*" + "utopia-php/database": "*" }, "require-dev": { "phpunit/phpunit": "^9.4", @@ -1704,7 +1704,7 @@ "upf", "utopia" ], - "time": "2021-07-01T14:20:57+00:00" + "time": "2021-07-02T15:33:05+00:00" }, { "name": "utopia-php/analytics", @@ -1763,16 +1763,16 @@ }, { "name": "utopia-php/audit", - "version": "dev-feat-utopia-db-integration", + "version": "dev-dev", "source": { "type": "git", "url": "https://github.com/kodumbeats/audit", - "reference": "25535352205f717c2320cd375242e5963677e344" + "reference": "6327d91307b260d9d7cf9fa0fbf4d08e408f470a" }, "require": { "ext-pdo": "*", "php": ">=7.4", - "utopia-php/database": "0.4.*" + "utopia-php/database": "*" }, "require-dev": { "phpunit/phpunit": "^9.3", @@ -1801,7 +1801,7 @@ "upf", "utopia" ], - "time": "2021-07-01T14:07:43+00:00" + "time": "2021-07-02T15:34:06+00:00" }, { "name": "utopia-php/cache", @@ -1962,17 +1962,11 @@ }, { "name": "utopia-php/database", - "version": "0.4.0", + "version": "dev-dev", "source": { "type": "git", - "url": "https://github.com/utopia-php/database.git", - "reference": "0b59d09a6622b472f82013623fd44e2b1b5504db" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/0b59d09a6622b472f82013623fd44e2b1b5504db", - "reference": "0b59d09a6622b472f82013623fd44e2b1b5504db", - "shasum": "" + "url": "https://github.com/kodumbeats/database", + "reference": "3397ca1042df8279e34e8f4d935d9d6c2938f2e8" }, "require": { "ext-mongodb": "*", @@ -1995,7 +1989,11 @@ "Utopia\\Database\\": "src/Database" } }, - "notification-url": "https://packagist.org/downloads/", + "autoload-dev": { + "psr-4": { + "Utopia\\Tests\\": "tests/Database" + } + }, "license": [ "MIT" ], @@ -2017,11 +2015,7 @@ "upf", "utopia" ], - "support": { - "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.4.0" - }, - "time": "2021-06-29T15:52:49+00:00" + "time": "2021-07-02T14:37:57+00:00" }, { "name": "utopia-php/domains", @@ -6235,7 +6229,8 @@ "minimum-stability": "stable", "stability-flags": { "utopia-php/abuse": 20, - "utopia-php/audit": 20 + "utopia-php/audit": 20, + "utopia-php/database": 20 }, "prefer-stable": false, "prefer-lowest": false, From 23dd38cedb87789ae3b8fe0dbb12a1e2b94f161b Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 2 Jul 2021 11:47:17 -0400 Subject: [PATCH 57/90] Add param for default values when creating collections --- app/config/collections2.php | 83 +++++++++++++++++++++++++++++++++++++ app/http.php | 1 + 2 files changed, 84 insertions(+) diff --git a/app/config/collections2.php b/app/config/collections2.php index b13009288..e6b2609d2 100644 --- a/app/config/collections2.php +++ b/app/config/collections2.php @@ -19,6 +19,7 @@ $collections = [ 'size' => 128, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -29,6 +30,7 @@ $collections = [ 'size' => 256, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -39,6 +41,7 @@ $collections = [ 'size' => Database::LENGTH_KEY, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -49,6 +52,7 @@ $collections = [ 'size' => Database::LENGTH_KEY, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -59,6 +63,7 @@ $collections = [ 'size' => 16384, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -69,6 +74,7 @@ $collections = [ 'size' => 0, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -79,6 +85,7 @@ $collections = [ 'size' => 256, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -89,6 +96,7 @@ $collections = [ 'size' => 256, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -99,6 +107,7 @@ $collections = [ 'size' => 256, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -109,6 +118,7 @@ $collections = [ 'size' => 256, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -119,6 +129,7 @@ $collections = [ 'size' => 256, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -129,6 +140,7 @@ $collections = [ 'size' => 256, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -139,6 +151,7 @@ $collections = [ 'size' => 16384, 'signed' => true, 'required' => false, + 'default' => null, 'array' => true, 'filters' => ['json'], ], @@ -149,6 +162,7 @@ $collections = [ 'size' => 16384, 'signed' => true, 'required' => false, + 'default' => null, 'array' => true, 'filters' => ['json'], ], @@ -159,6 +173,7 @@ $collections = [ 'size' => 16384, 'signed' => true, 'required' => false, + 'default' => null, 'array' => true, 'filters' => ['json'], ], @@ -169,6 +184,7 @@ $collections = [ 'size' => 16384, 'signed' => true, 'required' => false, + 'default' => null, 'array' => true, 'filters' => ['json'], ], @@ -179,6 +195,7 @@ $collections = [ 'size' => 16384, 'signed' => true, 'required' => false, + 'default' => null, 'array' => true, 'filters' => ['json'], ], @@ -206,6 +223,7 @@ $collections = [ 'size' => 256, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -216,6 +234,7 @@ $collections = [ 'size' => 1024, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -226,6 +245,7 @@ $collections = [ 'size' => 0, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -236,6 +256,7 @@ $collections = [ 'size' => 16384, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -246,6 +267,7 @@ $collections = [ 'size' => 0, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -256,6 +278,7 @@ $collections = [ 'size' => 16384, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => ['json'] ], @@ -266,6 +289,7 @@ $collections = [ 'size' => 0, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -276,6 +300,7 @@ $collections = [ 'size' => 0, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -286,6 +311,7 @@ $collections = [ 'size' => 0, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -296,6 +322,7 @@ $collections = [ 'size' => 16384, 'signed' => true, 'required' => false, + 'default' => null, 'array' => true, 'filters' => ['json'], ], @@ -306,6 +333,7 @@ $collections = [ 'size' => 16384, 'signed' => true, 'required' => false, + 'default' => null, 'array' => true, 'filters' => ['json'], ], @@ -316,6 +344,7 @@ $collections = [ 'size' => 16384, 'signed' => true, 'required' => false, + 'default' => null, 'array' => true, 'filters' => ['json'], ], @@ -343,6 +372,7 @@ $collections = [ 'size' => 128, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -353,6 +383,7 @@ $collections = [ 'size' => 0, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -363,6 +394,7 @@ $collections = [ 'size' => 0, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -390,6 +422,7 @@ $collections = [ 'size' => Database::LENGTH_KEY, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -400,6 +433,7 @@ $collections = [ 'size' => Database::LENGTH_KEY, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -410,6 +444,7 @@ $collections = [ 'size' => 128, 'signed' => true, 'required' => false, + 'default' => null, 'array' => true, 'filters' => [], ], @@ -420,6 +455,7 @@ $collections = [ 'size' => 0, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -430,6 +466,7 @@ $collections = [ 'size' => 0, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -440,6 +477,7 @@ $collections = [ 'size' => 0, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -450,6 +488,7 @@ $collections = [ 'size' => 256, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -491,6 +530,7 @@ $collections = [ 'size' => 0, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -502,6 +542,7 @@ $collections = [ 'size' => Database::LENGTH_KEY, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -512,6 +553,7 @@ $collections = [ 'size' => 2048, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -522,6 +564,7 @@ $collections = [ 'size' => 2048, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -532,6 +575,7 @@ $collections = [ 'size' => 2048, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -542,6 +586,7 @@ $collections = [ 'size' => 127, // https://tools.ietf.org/html/rfc4288#section-4.2 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -552,6 +597,7 @@ $collections = [ 'size' => 0, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -562,6 +608,7 @@ $collections = [ 'size' => 0, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -572,6 +619,7 @@ $collections = [ 'size' => 255, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -582,6 +630,7 @@ $collections = [ 'size' => 2048, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -592,6 +641,7 @@ $collections = [ 'size' => 64, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -602,6 +652,7 @@ $collections = [ 'size' => 64, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -612,6 +663,7 @@ $collections = [ 'size' => 2048, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -622,6 +674,7 @@ $collections = [ 'size' => 2048, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -656,6 +709,7 @@ $collections = [ 'size' => 128, 'signed' => true, 'required' => false, + 'default' => null, 'array' => true, 'filters' => [], ], @@ -666,6 +720,7 @@ $collections = [ 'size' => 0, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -676,6 +731,7 @@ $collections = [ 'size' => 0, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -687,6 +743,7 @@ $collections = [ 'size' => Database::LENGTH_KEY, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -697,6 +754,7 @@ $collections = [ 'size' => 2048, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -707,6 +765,7 @@ $collections = [ 'size' => 2048, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -717,6 +776,7 @@ $collections = [ 'size' => Database::LENGTH_KEY, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -727,6 +787,7 @@ $collections = [ 'size' => 8192, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => ['json', 'encrypt'], ], @@ -737,6 +798,7 @@ $collections = [ 'size' => 256, 'signed' => true, 'required' => false, + 'default' => null, 'array' => true, 'filters' => [], ], @@ -747,6 +809,7 @@ $collections = [ 'size' => 128, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -757,6 +820,7 @@ $collections = [ 'size' => 0, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -767,6 +831,7 @@ $collections = [ 'size' => 0, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -777,6 +842,7 @@ $collections = [ 'size' => 0, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -804,6 +870,7 @@ $collections = [ 'size' => 0, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -814,6 +881,7 @@ $collections = [ 'size' => Database::LENGTH_KEY, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -825,6 +893,7 @@ $collections = [ 'size' => 2048, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -835,6 +904,7 @@ $collections = [ 'size' => 2048, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -845,6 +915,7 @@ $collections = [ 'size' => 0, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -872,6 +943,7 @@ $collections = [ 'size' => 0, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -882,6 +954,7 @@ $collections = [ 'size' => Database::LENGTH_KEY, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -892,6 +965,7 @@ $collections = [ 'size' => Database::LENGTH_KEY, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -903,6 +977,7 @@ $collections = [ 'size' => 128, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -913,6 +988,7 @@ $collections = [ 'size' => 128, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -923,6 +999,7 @@ $collections = [ 'size' => 16384, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -933,6 +1010,7 @@ $collections = [ 'size' => 16384, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -943,6 +1021,7 @@ $collections = [ 'size' => 0, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -953,6 +1032,7 @@ $collections = [ 'size' => 0, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ], @@ -984,6 +1064,7 @@ foreach ($providers as $index => $provider) { 'size' => 16384, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ]; @@ -995,6 +1076,7 @@ foreach ($providers as $index => $provider) { 'size' => 16384, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ]; @@ -1008,6 +1090,7 @@ foreach ($auth as $index => $method) { 'size' => 0, 'signed' => true, 'required' => false, + 'default' => null, 'array' => false, 'filters' => [], ]; diff --git a/app/http.php b/app/http.php index 69dbbd98b..daf5d4d03 100644 --- a/app/http.php +++ b/app/http.php @@ -105,6 +105,7 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) { $attribute['type'], $attribute['size'], $attribute['required'], + $attribute['default'], $attribute['signed'], $attribute['array'], $attribute['filters'], From 145203663dff4af65177276828d5cc1ff083ccd5 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 2 Jul 2021 13:29:03 -0400 Subject: [PATCH 58/90] Pass default param where required --- app/controllers/api/database.php | 7 +++++-- app/controllers/api/projects.php | 1 + app/workers/database.php | 3 ++- tests/e2e/Services/Database/DatabaseBase.php | 1 + 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index f9bbaeee6..13b419eb7 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -6,6 +6,7 @@ use Utopia\Validator\Boolean; use Utopia\Validator\Numeric; use Utopia\Validator\Range; use Utopia\Validator\WhiteList; +use Utopia\Validator\Wildcard; use Utopia\Validator\Text; use Utopia\Validator\ArrayList; use Utopia\Validator\JSON; @@ -246,12 +247,13 @@ App::post('/v1/database/collections/:collectionId/attributes') ->param('type', null, new Text(256), 'Attribute type.') ->param('size', null, new Numeric(), 'Attribute size for text attributes, in number of characters. For integers, floats, or bools, use 0.') ->param('required', null, new Boolean(), 'Is attribute required?') + ->param('default', null, new Wildcard(), '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('dbForExternal') ->inject('database') ->inject('audits') - ->action(function ($collectionId, $id, $type, $size, $required, $array, $response, $dbForExternal, $database, $audits) { + ->action(function ($collectionId, $id, $type, $size, $required, $default, $array, $response, $dbForExternal, $database, $audits) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForExternal*/ /** @var Appwrite\Event\Event $database */ @@ -267,7 +269,7 @@ App::post('/v1/database/collections/:collectionId/attributes') $signed = true; $filters = []; - $success = $dbForExternal->addAttributeInQueue($collectionId, $id, $type, $size, $required, $signed, $array, $filters); + $success = $dbForExternal->addAttributeInQueue($collectionId, $id, $type, $size, $required, $default, $signed, $array, $filters); // Database->addAttributeInQueue() does not return a document // So we need to create one for the response @@ -279,6 +281,7 @@ App::post('/v1/database/collections/:collectionId/attributes') 'type' => $type, 'size' => $size, 'required' => $required, + 'default' => $default, 'signed' => $signed, 'array' => $array, 'filters' => $filters diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index bdcb89bb3..2eeac4881 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -112,6 +112,7 @@ App::post('/v1/projects') $attribute['type'], $attribute['size'], $attribute['required'], + $attribute['default'], $attribute['signed'], $attribute['array'], $attribute['filters'], diff --git a/app/workers/database.php b/app/workers/database.php index 8b93bcfd4..900335f82 100644 --- a/app/workers/database.php +++ b/app/workers/database.php @@ -72,11 +72,12 @@ class DatabaseV1 extends Worker $type = $attribute->getAttribute('type', ''); $size = $attribute->getAttribute('size', 0); $required = $attribute->getAttribute('required', false); + $default = $attribute->getAttribute('default', null); $signed = $attribute->getAttribute('signed', true); $array = $attribute->getAttribute('array', false); $filters = $attribute->getAttribute('filters', []); - $success = $dbForExternal->createAttribute($collectionId, $id, $type, $size, $required, $signed, $array, $filters); + $success = $dbForExternal->createAttribute($collectionId, $id, $type, $size, $required, $default, $signed, $array, $filters); if ($success) { $removed = $dbForExternal->removeAttributeInQueue($collectionId, $id); } diff --git a/tests/e2e/Services/Database/DatabaseBase.php b/tests/e2e/Services/Database/DatabaseBase.php index 7dad8b14c..89567aae3 100644 --- a/tests/e2e/Services/Database/DatabaseBase.php +++ b/tests/e2e/Services/Database/DatabaseBase.php @@ -63,6 +63,7 @@ trait DatabaseBase 'type' => 'string', 'size' => 256, 'required' => false, + 'default' => null, 'array' => true, ]); From e9561a68dc95840e645a73e7ddc75669e07603ae Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 2 Jul 2021 13:29:39 -0400 Subject: [PATCH 59/90] Get db and cache from pool via coroutine --- app/workers/database.php | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/app/workers/database.php b/app/workers/database.php index 900335f82..49f19efdb 100644 --- a/app/workers/database.php +++ b/app/workers/database.php @@ -140,10 +140,18 @@ class DatabaseV1 extends Worker protected function getInternalDB($projectId): Database { global $register; - - $cache = new Cache(new RedisCache($register->get('cache'))); - $dbForInternal = new Database(new MariaDB($register->get('db')), $cache); - $dbForInternal->setNamespace('project_'.$projectId.'_internal'); // Main DB + + $dbForInternal = null; + + go(function() use ($register, $projectId, &$dbForInternal) { + $db = $register->get('dbPool')->get(); + $redis = $register->get('redisPool')->get(); + + $cache = new Cache(new RedisCache($redis)); + $dbForInternal = new Database(new MariaDB($db), $cache); + $dbForInternal->setNamespace('project_'.$projectId.'_internal'); // Main DB + + }); return $dbForInternal; } @@ -156,10 +164,18 @@ class DatabaseV1 extends Worker protected function getExternalDB($projectId): Database { global $register; - - $cache = new Cache(new RedisCache($register->get('cache'))); - $dbForExternal = new Database(new MariaDB($register->get('db')), $cache); - $dbForExternal->setNamespace('project_'.$projectId.'_external'); // Main DB + + $dbForExternal = null; + + go(function() use ($register, $projectId, &$dbForExternal) { + $db = $register->get('dbPool')->get(); + $redis = $register->get('redisPool')->get(); + + $cache = new Cache(new RedisCache($redis)); + $dbForExternal = new Database(new MariaDB($db), $cache); + $dbForExternal->setNamespace('project_'.$projectId.'_external'); // Main DB + + }); return $dbForExternal; } From bafde0aa292ec59787d9359aef3441bef52c2738 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 2 Jul 2021 16:21:06 -0400 Subject: [PATCH 60/90] Remove unneeded param from callback --- 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 13b419eb7..509d3154a 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -145,7 +145,7 @@ App::put('/v1/database/collections/:collectionId') ->inject('response') ->inject('dbForExternal') ->inject('audits') - ->action(function ($collectionId, $name, $read, $write, $rules, $response, $dbForExternal, $audits) { + ->action(function ($collectionId, $name, $read, $write, $response, $dbForExternal, $audits) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForExternal */ /** @var Appwrite\Event\Event $audits */ From db4e6c93be4609cf399015808a6132fdd6fc954d Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 2 Jul 2021 16:21:46 -0400 Subject: [PATCH 61/90] Added events for attributes and indexes --- app/config/events.php | 20 ++++++++++++++++++++ tests/e2e/Scopes/ProjectCustom.php | 4 ++++ 2 files changed, 24 insertions(+) diff --git a/app/config/events.php b/app/config/events.php index b27a5eafb..def827916 100644 --- a/app/config/events.php +++ b/app/config/events.php @@ -82,6 +82,26 @@ return [ 'model' => Response::MODEL_COLLECTION, 'note' => '', ], + 'database.attributes.create' => [ + 'description' => 'This event triggers when a collection attribute is created.', + 'model' => Response::MODEL_ATTRIBUTE, + 'note' => '', + ], + 'database.attributes.delete' => [ + 'description' => 'This event triggers when a collection attribute is deleted.', + 'model' => Response::MODEL_ATTRIBUTE, + 'note' => '', + ], + 'database.indexes.create' => [ + 'description' => 'This event triggers when a collection index is created.', + 'model' => Response::MODEL_INDEX, + 'note' => '', + ], + 'database.indexes.delete' => [ + 'description' => 'This event triggers when a collection index is deleted.', + 'model' => Response::MODEL_INDEX, + 'note' => '', + ], 'database.documents.create' => [ 'description' => 'This event triggers when a database document is created.', 'model' => Response::MODEL_DOCUMENT, diff --git a/tests/e2e/Scopes/ProjectCustom.php b/tests/e2e/Scopes/ProjectCustom.php index 431762b6a..51f318318 100644 --- a/tests/e2e/Scopes/ProjectCustom.php +++ b/tests/e2e/Scopes/ProjectCustom.php @@ -114,6 +114,10 @@ trait ProjectCustom 'database.collections.create', 'database.collections.update', 'database.collections.delete', + 'database.attributes.create', + 'database.attributes.delete', + 'database.indexes.create', + 'database.indexes.delete', 'database.documents.create', 'database.documents.update', 'database.documents.delete', From 95dad203b8f08350856b44b6f69605b71c194157 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 2 Jul 2021 16:22:12 -0400 Subject: [PATCH 62/90] Test attribute creation webhooks --- tests/e2e/Services/Webhooks/WebhooksBase.php | 73 ++++++++++++++----- .../Webhooks/WebhooksCustomServerTest.php | 60 +++------------ 2 files changed, 62 insertions(+), 71 deletions(-) diff --git a/tests/e2e/Services/Webhooks/WebhooksBase.php b/tests/e2e/Services/Webhooks/WebhooksBase.php index dfbdc524c..dcf509fdc 100644 --- a/tests/e2e/Services/Webhooks/WebhooksBase.php +++ b/tests/e2e/Services/Webhooks/WebhooksBase.php @@ -20,24 +20,6 @@ trait WebhooksBase 'name' => 'Actors', 'read' => ['role:all'], 'write' => ['role:all'], - 'rules' => [ - [ - 'label' => 'First Name', - 'key' => 'firstName', - 'type' => 'text', - 'default' => '', - 'required' => true, - 'array' => false - ], - [ - 'label' => 'Last Name', - 'key' => 'lastName', - 'type' => 'text', - 'default' => '', - 'required' => true, - 'array' => false - ], - ], ]); $this->assertEquals($actors['headers']['status-code'], 201); @@ -55,12 +37,10 @@ trait WebhooksBase $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), true); $this->assertNotEmpty($webhook['data']['$id']); $this->assertEquals($webhook['data']['name'], 'Actors'); - $this->assertIsArray($webhook['data']['$permissions']); $this->assertIsArray($webhook['data']['$read']); $this->assertIsArray($webhook['data']['$write']); $this->assertCount(1, $webhook['data']['$read']); $this->assertCount(1, $webhook['data']['$write']); - $this->assertCount(2, $webhook['data']['rules']); return array_merge(['actorsId' => $actors['body']['$id']]); } @@ -68,6 +48,59 @@ trait WebhooksBase /** * @depends testCreateCollection */ + public function testCreateAttributes(array $data): array + { + $firstName = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/attributes', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'id' => 'firstName', + 'type' => 'string', + 'size' => 256, + 'required' => true, + ]); + + $lastName = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/attributes', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'id' => 'lastName', + 'type' => 'string', + 'size' => 256, + 'required' => true, + ]); + + $this->assertEquals($firstName['headers']['status-code'], 201); + $this->assertEquals($firstName['body']['$collection'], $data['actorsId']); + $this->assertEquals($firstName['body']['$id'], 'firstName'); + $this->assertEquals($lastName['headers']['status-code'], 201); + $this->assertEquals($lastName['body']['$collection'], $data['actorsId']); + $this->assertEquals($lastName['body']['$id'], 'lastName'); + + // wait for database worker to kick in + sleep(5); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'database.attributes.create'); + $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'] ?? ''), ('server' === $this->getSide())); + $this->assertNotEmpty($webhook['data']['$id']); + $this->assertEquals($webhook['data']['$id'], 'lastName'); + + return $data; + } + + /** + * @depends testCreateAttributes + */ public function testCreateDocument(array $data): array { $document = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/documents', array_merge([ diff --git a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php index dbb44e0de..55be01e8e 100644 --- a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php +++ b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php @@ -15,7 +15,7 @@ class WebhooksCustomServerTest extends Scope use SideServer; /** - * @depends testCreateCollection + * @depends testCreateAttributes */ public function testUpdateCollection($data): array { @@ -28,26 +28,6 @@ class WebhooksCustomServerTest extends Scope 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ 'name' => 'Actors1', - 'read' => ['role:all'], - 'write' => ['role:all'], - 'rules' => [ - [ - 'label' => 'First Name', - 'key' => 'firstName', - 'type' => 'text', - 'default' => '', - 'required' => true, - 'array' => false - ], - [ - 'label' => 'Last Name', - 'key' => 'lastName', - 'type' => 'text', - 'default' => '', - 'required' => true, - 'array' => false - ], - ], ]); $this->assertEquals($actors['headers']['status-code'], 200); @@ -65,12 +45,10 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), true); $this->assertNotEmpty($webhook['data']['$id']); $this->assertEquals($webhook['data']['name'], 'Actors1'); - $this->assertIsArray($webhook['data']['$permissions']); - $this->assertIsArray($webhook['data']['$permissions']['read']); - $this->assertIsArray($webhook['data']['$permissions']['write']); - $this->assertCount(1, $webhook['data']['$permissions']['read']); - $this->assertCount(1, $webhook['data']['$permissions']['write']); - $this->assertCount(2, $webhook['data']['rules']); + $this->assertIsArray($webhook['data']['$read']); + $this->assertIsArray($webhook['data']['$write']); + $this->assertCount(1, $webhook['data']['$read']); + $this->assertCount(1, $webhook['data']['$write']); return array_merge(['actorsId' => $actors['body']['$id']]); } @@ -88,24 +66,6 @@ class WebhooksCustomServerTest extends Scope 'name' => 'Demo', 'read' => ['role:all'], 'write' => ['role:all'], - 'rules' => [ - [ - 'label' => 'First Name', - 'key' => 'firstName', - 'type' => 'text', - 'default' => '', - 'required' => true, - 'array' => false - ], - [ - 'label' => 'Last Name', - 'key' => 'lastName', - 'type' => 'text', - 'default' => '', - 'required' => true, - 'array' => false - ], - ], ]); $this->assertEquals($actors['headers']['status-code'], 201); @@ -131,12 +91,10 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), true); $this->assertNotEmpty($webhook['data']['$id']); $this->assertEquals($webhook['data']['name'], 'Demo'); - $this->assertIsArray($webhook['data']['$permissions']); - $this->assertIsArray($webhook['data']['$permissions']['read']); - $this->assertIsArray($webhook['data']['$permissions']['write']); - $this->assertCount(1, $webhook['data']['$permissions']['read']); - $this->assertCount(1, $webhook['data']['$permissions']['write']); - $this->assertCount(2, $webhook['data']['rules']); + $this->assertIsArray($webhook['data']['$read']); + $this->assertIsArray($webhook['data']['$write']); + $this->assertCount(1, $webhook['data']['$read']); + $this->assertCount(1, $webhook['data']['$write']); return []; } From 760a0f9c7919fcffd69fd31355d3d85970d88cdb Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 2 Jul 2021 16:39:53 -0400 Subject: [PATCH 63/90] Update env to runtime --- app/config/collections2.php | 2 +- tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/config/collections2.php b/app/config/collections2.php index e6b2609d2..d1e5539dd 100644 --- a/app/config/collections2.php +++ b/app/config/collections2.php @@ -759,7 +759,7 @@ $collections = [ 'filters' => [], ], [ - '$id' => 'env', + '$id' => 'runtime', 'type' => Database::VAR_STRING, 'format' => '', 'size' => 2048, diff --git a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php index 55be01e8e..19ecb04de 100644 --- a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php +++ b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php @@ -264,8 +264,8 @@ class WebhooksCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'name' => 'Test', - 'env' => 'php-8.0', - 'runtime' => ['role:all'], + 'execute' => ['role:all'], + 'runtime' => 'php-8.0', 'timeout' => 10, ]); From 05fb04fdc2b08bf1339c9da87342a77cbc6352f8 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 2 Jul 2021 18:22:36 -0400 Subject: [PATCH 64/90] Fix webhook tests --- tests/e2e/Services/Database/DatabaseBase.php | 4 +++- tests/e2e/Services/Webhooks/WebhooksBase.php | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/e2e/Services/Database/DatabaseBase.php b/tests/e2e/Services/Database/DatabaseBase.php index 89567aae3..f156dd71f 100644 --- a/tests/e2e/Services/Database/DatabaseBase.php +++ b/tests/e2e/Services/Database/DatabaseBase.php @@ -90,7 +90,7 @@ trait DatabaseBase $this->assertEquals($actors['body']['array'], true); // wait for database worker to create attributes - sleep(5); + sleep(10); $movies = $this->client->call(Client::METHOD_GET, '/database/collections/' . $data['moviesId'], array_merge([ 'content-type' => 'application/json', @@ -101,6 +101,8 @@ trait DatabaseBase $this->assertEquals($movies['body']['$id'], $title['body']['$collection']); $this->assertEquals($movies['body']['$id'], $releaseYear['body']['$collection']); $this->assertEquals($movies['body']['$id'], $actors['body']['$collection']); + $this->assertIsArray($movies['body']['attributesInQueue']); + $this->assertCount(0, $movies['body']['attributesInQueue']); $this->assertIsArray($movies['body']['attributes']); $this->assertCount(3, $movies['body']['attributes']); $this->assertEquals($movies['body']['attributes'][0]['$id'], $title['body']['$id']); diff --git a/tests/e2e/Services/Webhooks/WebhooksBase.php b/tests/e2e/Services/Webhooks/WebhooksBase.php index dcf509fdc..1dd8cbd48 100644 --- a/tests/e2e/Services/Webhooks/WebhooksBase.php +++ b/tests/e2e/Services/Webhooks/WebhooksBase.php @@ -91,7 +91,6 @@ trait WebhooksBase $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'] ?? ''), ('server' === $this->getSide())); $this->assertNotEmpty($webhook['data']['$id']); $this->assertEquals($webhook['data']['$id'], 'lastName'); From dfa7516b153f5bf8bf379041dd7500f28fb6a228 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 2 Jul 2021 18:22:49 -0400 Subject: [PATCH 65/90] Add missing typehint --- app/controllers/api/database.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 509d3154a..01336c100 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -467,6 +467,7 @@ App::post('/v1/database/collections/:collectionId/indexes') ->action(function ($collectionId, $id, $type, $attributes, $orders, $response, $dbForExternal, $database, $audits) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForExternal */ + /** @var Appwrite\Event\Event $database */ /** @var Appwrite\Event\Event $audits */ $collection = $dbForExternal->getCollection($collectionId); From 50a3493d6901f37fa822dcec7b9fb4d2a49aee36 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 2 Jul 2021 18:23:58 -0400 Subject: [PATCH 66/90] Use attribute size as index length --- app/controllers/api/database.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 01336c100..f4dd544be 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -476,9 +476,31 @@ App::post('/v1/database/collections/:collectionId/indexes') throw new Exception('Collection not found', 404); } + if (\count($attributes) !== \count($orders)) { + throw new Exception('Must have one order per attribute', 400); + } + + $oldAttributes = $collection->getAttribute('attributes'); + // 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, '$id')); + + if ($attributeIndex === false) { + throw new Exception('Unknown attribute: ' . $attribute, 400); + } + + $type = $oldAttributes[$attributeIndex]['type']; + $size = $oldAttributes[$attributeIndex]['size']; + + // Only set length for indexes on strings + $length[$key] = ($type === Database::VAR_STRING) ? $size : null; + } + $success = $dbForExternal->addIndexInQueue($collectionId, $id, $type, $attributes, $lengths, $orders); // Database->createIndex() does not return a document From 7e2ca3ba97afa6bcd8533d4e21c85a6dba398562 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 2 Jul 2021 18:48:58 -0400 Subject: [PATCH 67/90] Test webhooks for indexes --- .../Webhooks/WebhooksCustomServerTest.php | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php index 19ecb04de..b79645dfd 100644 --- a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php +++ b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php @@ -53,6 +53,64 @@ class WebhooksCustomServerTest extends Scope return array_merge(['actorsId' => $actors['body']['$id']]); } + /** + * @depends testCreateAttributes + */ + public function testCreateDeleteIndexes($data): array + { + $index = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/indexes', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'id' => 'fullname', + 'type' => 'text', + 'attributes' => ['lastName', 'firstName'], + 'orders' => ['ASC', 'ASC'], + ]); + + $this->assertEquals($index['headers']['status-code'], 201); + $this->assertEquals($index['body']['$collection'], $data['actorsId']); + $this->assertEquals($index['body']['$id'], 'fullname'); + + // wait for database worker to create index + sleep(5); + + $webhook = $this->getLastRequest(); + + $this->assertEquals($webhook['method'], 'POST'); + $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); + $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); + $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Event'], 'database.indexes.create'); + $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); + + // 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'] + ])); + + // wait for database worker to remove index + sleep(5); + + $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); + + return $data; + } + public function testDeleteCollection(): array { /** From 860a2c761654700547ec2bc1f7fa458cf93f52b3 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 2 Jul 2021 20:20:09 -0400 Subject: [PATCH 68/90] Fix index and attribute routes for tests --- app/controllers/api/database.php | 23 +++++++++++--------- tests/e2e/Services/Webhooks/WebhooksBase.php | 4 +++- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index f4dd544be..6d31a2051 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -459,7 +459,9 @@ App::post('/v1/database/collections/:collectionId/indexes') ->param('id', null, new Key(), 'Index ID.') ->param('type', null, new WhiteList([Database::INDEX_KEY, Database::INDEX_FULLTEXT, Database::INDEX_UNIQUE, Database::INDEX_SPATIAL, Database::INDEX_ARRAY]), 'Index type.') ->param('attributes', null, new ArrayList(new Key()), 'Array of attributes to index.') - ->param('orders', [], new ArrayList(new WhiteList(['ASC', 'DESC'])), 'Array of index orders.', true) + // TODO@kodumbeats debug below + // ->param('orders', [], new ArrayList(new WhiteList(['ASC', 'DESC'], false, Database::VAR_STRING)), 'Array of index orders.', true) + ->param('orders', [], new ArrayList(new Text(4)), 'Array of index orders.', true) ->inject('response') ->inject('dbForExternal') ->inject('database') @@ -476,17 +478,18 @@ App::post('/v1/database/collections/:collectionId/indexes') throw new Exception('Collection not found', 404); } - if (\count($attributes) !== \count($orders)) { - throw new Exception('Must have one order per attribute', 400); - } - - $oldAttributes = $collection->getAttribute('attributes'); + // Convert Document[] to array of attribute metadata + $oldAttributes = \array_map(function ($a) { + return $a->getArrayCopy(); + }, $collection->getAttribute('attributes')); // lengths hidden by default $lengths = []; + var_dump($oldAttributes); + // set attribute size as length for strings, null otherwise - foreach ($attributes as $key => &$attribute) { + foreach ($attributes as $key => $attribute) { // find attribute metadata in collection document $attributeIndex = \array_search($attribute, array_column($oldAttributes, '$id')); @@ -494,11 +497,11 @@ App::post('/v1/database/collections/:collectionId/indexes') throw new Exception('Unknown attribute: ' . $attribute, 400); } - $type = $oldAttributes[$attributeIndex]['type']; - $size = $oldAttributes[$attributeIndex]['size']; + $attributeType = $oldAttributes[$attributeIndex]['type']; + $attributeSize = $oldAttributes[$attributeIndex]['size']; // Only set length for indexes on strings - $length[$key] = ($type === Database::VAR_STRING) ? $size : null; + $lengths[$key] = ($attributeType === Database::VAR_STRING) ? $attributeSize : null; } $success = $dbForExternal->addIndexInQueue($collectionId, $id, $type, $attributes, $lengths, $orders); diff --git a/tests/e2e/Services/Webhooks/WebhooksBase.php b/tests/e2e/Services/Webhooks/WebhooksBase.php index 1dd8cbd48..bda83bb06 100644 --- a/tests/e2e/Services/Webhooks/WebhooksBase.php +++ b/tests/e2e/Services/Webhooks/WebhooksBase.php @@ -80,7 +80,7 @@ trait WebhooksBase $this->assertEquals($lastName['body']['$id'], 'lastName'); // wait for database worker to kick in - sleep(5); + sleep(10); $webhook = $this->getLastRequest(); @@ -93,6 +93,8 @@ trait WebhooksBase $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); $this->assertNotEmpty($webhook['data']['$id']); $this->assertEquals($webhook['data']['$id'], 'lastName'); + + // TODO@kodumbeats test webhook for removing attribute return $data; } From 081f2f83f101b62d4612ac3c956f86e2e3dc32c5 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 5 Jul 2021 14:28:38 -0400 Subject: [PATCH 69/90] Improve examples --- src/Appwrite/Utopia/Response/Model/Attribute.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Appwrite/Utopia/Response/Model/Attribute.php b/src/Appwrite/Utopia/Response/Model/Attribute.php index 1dceb4999..b8763be6f 100644 --- a/src/Appwrite/Utopia/Response/Model/Attribute.php +++ b/src/Appwrite/Utopia/Response/Model/Attribute.php @@ -14,7 +14,7 @@ class Attribute extends Model 'type' => self::TYPE_STRING, 'description' => 'Collection ID.', 'default' => '', - 'example' => '60ccf71b98a2d', + 'example' => '5e5ea5c16d55', ]) ->addRule('$id', [ 'type' => self::TYPE_STRING, @@ -26,19 +26,19 @@ class Attribute extends Model 'type' => self::TYPE_STRING, 'description' => 'Attribute type.', 'default' => '', - 'example' => 'integer', + 'example' => 'string', ]) ->addRule('size', [ 'type' => self::TYPE_STRING, 'description' => 'Attribute size.', 'default' => 0, - 'example' => 0, + 'example' => 128, ]) ->addRule('required', [ 'type' => self::TYPE_BOOLEAN, 'description' => 'Is attribute required?', 'default' => false, - 'example' => false, + 'example' => true, ]) ->addRule('signed', [ 'type' => self::TYPE_BOOLEAN, From e26d3796183821c90105fb748b18ab905ac56e9a Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 5 Jul 2021 14:29:21 -0400 Subject: [PATCH 70/90] Test for webhook indexes.delete not complete --- .../Webhooks/WebhooksCustomServerTest.php | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php index b79645dfd..4e4c768e6 100644 --- a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php +++ b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php @@ -87,26 +87,27 @@ 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'] - ])); + // $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'] + // ])); - // wait for database worker to remove index - sleep(5); + // // 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 4a9007601322c6e02a261136dc56138981e58794 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 5 Jul 2021 15:15:39 -0400 Subject: [PATCH 71/90] Cleanup code comments --- app/controllers/api/database.php | 27 ++------------------------- 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 6d31a2051..825355676 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -51,9 +51,8 @@ App::post('/v1/database/collections') $write = (is_null($write)) ? ($collection->getWrite() ?? []) : $write; // By default inherit write permissions $collection->setAttribute('name', $name); - // TODO@kodumbeats Use the default permissions from Utopia for now - // $collection->setAttribute('$read', $read); - // $collection->setAttribute('$write', $write); + $collection->setAttribute('$read', $read); + $collection->setAttribute('$write', $write); $dbForExternal->updateDocument(Database::COLLECTIONS, $id, $collection); @@ -723,28 +722,6 @@ App::post('/v1/database/collections/:collectionId/documents') $data['$read'] = (is_null($read) && !$user->isEmpty()) ? ['user:'.$user->getId()] : $read ?? []; // By default set read permissions for user $data['$write'] = (is_null($write) && !$user->isEmpty()) ? ['user:'.$user->getId()] : $write ?? []; // By default set write permissions for user - /** - * TODO@kodumbeats How to assign default values to attributes - */ - // foreach ($collection->getAttributes() as $key => $attribute) { - // $key = $attribute['$id'] ?? ''; - // if ($attribute['array'] === true) { - // $default = []; - // } elseif ($attribute['type'] === Database::VAR_STRING) { - // $default = ''; - // } elseif ($attribute['type'] === Database::VAR_BOOLEAN) { - // $default = false; - // } elseif ($attribute['type'] === Database::VAR_INTEGER - // || $attribute['type'] === Database::VAR_FLOAT) { - // $default = 0; - // } - - // if (!isset($data[$key])) { - // $data[$key] = $default; - // } - // } - - // TODO@kodumbeats catch other exceptions try { $document = $dbForExternal->createDocument($collectionId, new Document($data)); } catch (StructureException $exception) { From 64d1913a03fc18cd1d5879dfbc579dfdcb2496b3 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 5 Jul 2021 15:19:18 -0400 Subject: [PATCH 72/90] Keep search param in listCollections --- app/controllers/api/database.php | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 825355676..8895e38c2 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -77,24 +77,21 @@ App::get('/v1/database/collections') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_COLLECTION_LIST) - ->param('queries', [], new ArrayList(new Text(128)), 'Array of query strings.', true) + ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) ->param('limit', 25, new Range(0, 100), 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) ->param('offset', 0, new Range(0, 40000), 'Results offset. The default value is 0. Use this param to manage pagination.', true) + ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true) ->inject('response') ->inject('dbForExternal') - ->action(function ($queries, $limit, $offset, $response, $dbForExternal) { + ->action(function ($search, $limit, $offset, $orderType, $response, $dbForExternal) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForExternal */ - $queries = \array_map(function ($query) { - return Query::parse($query); - }, $queries); - - $collections = $dbForExternal->find(Database::COLLECTIONS, $queries, $limit, $offset); + $queries = ($search) ? [new Query('name', Query::TYPE_SEARCH, [$search])] : []; $response->dynamic2(new Document([ - 'collections' => $collections, - 'sum' => \count($collections), + 'collections' => $dbForInternal->find(Database::COLLECTIONS, $queries, $limit, $offset, ['_id'], [$orderType]), + 'sum' => $dbForInternal->count(Database::COLLECTIONS, $queries, APP_LIMIT_COUNT), ]), Response::MODEL_COLLECTION_LIST); }); From 06c276d855eb8bb91c0fb5af2c7bccb8316f39bf Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 5 Jul 2021 16:27:20 -0400 Subject: [PATCH 73/90] Tighten up validation --- app/controllers/api/database.php | 50 +++++++++++++------- tests/e2e/Services/Database/DatabaseBase.php | 8 ++-- 2 files changed, 36 insertions(+), 22 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 8895e38c2..f47f764a5 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -3,6 +3,7 @@ use Utopia\App; use Utopia\Exception; use Utopia\Validator\Boolean; +use Utopia\Validator\Integer; use Utopia\Validator\Numeric; use Utopia\Validator\Range; use Utopia\Validator\WhiteList; @@ -11,7 +12,10 @@ use Utopia\Validator\Text; use Utopia\Validator\ArrayList; use Utopia\Validator\JSON; use Utopia\Database\Validator\Key; -use Appwrite\Database\Validator\UID; +use Utopia\Database\Validator\Permissions; +use Utopia\Database\Validator\QueryValidator; +use Utopia\Database\Validator\Queries as QueriesValidator; +use Utopia\Database\Validator\UID; use Utopia\Database\Exception\Authorization as AuthorizationException; use Utopia\Database\Exception\Structure as StructureException; use Appwrite\Utopia\Response; @@ -32,8 +36,8 @@ App::post('/v1/database/collections') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_COLLECTION) ->param('name', '', new Text(128), 'Collection name. Max length: 128 chars.') - ->param('read', null, new ArrayList(new Text(64)), 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.') - ->param('write', null, new ArrayList(new Text(64)), 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.') + ->param('read', null, new Permissions(), 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.') + ->param('write', null, new Permissions(), 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.') ->inject('response') ->inject('dbForExternal') ->inject('audits') @@ -43,7 +47,7 @@ App::post('/v1/database/collections') /** @var Appwrite\Event\Event $audits */ $id = $dbForExternal->getId(); - + $collection = $dbForExternal->createCollection($id); // TODO@kodumbeats what should the default permissions be? @@ -90,8 +94,8 @@ App::get('/v1/database/collections') $queries = ($search) ? [new Query('name', Query::TYPE_SEARCH, [$search])] : []; $response->dynamic2(new Document([ - 'collections' => $dbForInternal->find(Database::COLLECTIONS, $queries, $limit, $offset, ['_id'], [$orderType]), - 'sum' => $dbForInternal->count(Database::COLLECTIONS, $queries, APP_LIMIT_COUNT), + 'collections' => $dbForExternal->find(Database::COLLECTIONS, $queries, $limit, $offset, ['_id'], [$orderType]), + 'sum' => $dbForExternal->count(Database::COLLECTIONS, $queries, APP_LIMIT_COUNT), ]), Response::MODEL_COLLECTION_LIST); }); @@ -136,8 +140,8 @@ App::put('/v1/database/collections/:collectionId') ->label('sdk.response.model', Response::MODEL_COLLECTION) ->param('collectionId', '', new UID(), 'Collection unique ID.') ->param('name', null, new Text(128), 'Collection name. Max length: 128 chars.') - ->param('read', null, new ArrayList(new Text(64)), 'An array of strings with read permissions. By default inherits the existing read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true) - ->param('write', null, new ArrayList(new Text(64)), 'An array of strings with write permissions. By default inherits the existing write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true) + ->param('read', null, new Permissions(), 'An array of strings with read permissions. By default inherits the existing read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true) + ->param('write', null, new Permissions(), 'An array of strings with write permissions. By default inherits the existing write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true) ->inject('response') ->inject('dbForExternal') ->inject('audits') @@ -240,8 +244,8 @@ App::post('/v1/database/collections/:collectionId/attributes') ->label('sdk.response.model', Response::MODEL_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('id', '', new Key(), 'Attribute ID.') - ->param('type', null, new Text(256), 'Attribute type.') - ->param('size', null, new Numeric(), 'Attribute size for text attributes, in number of characters. For integers, floats, or bools, use 0.') + ->param('type', null, new Text(8), 'Attribute type.') + ->param('size', null, new Integer(), 'Attribute size for text attributes, in number of characters. For integers, floats, or bools, use 0.') ->param('required', null, new Boolean(), 'Is attribute required?') ->param('default', null, new Wildcard(), '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) @@ -456,8 +460,8 @@ App::post('/v1/database/collections/:collectionId/indexes') ->param('type', null, new WhiteList([Database::INDEX_KEY, Database::INDEX_FULLTEXT, Database::INDEX_UNIQUE, Database::INDEX_SPATIAL, Database::INDEX_ARRAY]), 'Index type.') ->param('attributes', null, new ArrayList(new Key()), 'Array of attributes to index.') // TODO@kodumbeats debug below - // ->param('orders', [], new ArrayList(new WhiteList(['ASC', 'DESC'], false, Database::VAR_STRING)), 'Array of index orders.', true) - ->param('orders', [], new ArrayList(new Text(4)), 'Array of index orders.', true) + ->param('orders', [], new ArrayList(new WhiteList(['ASC', 'DESC'], false, Database::VAR_STRING)), 'Array of index orders.', true) + // ->param('orders', [], new ArrayList(new Text(4)), 'Array of index orders.', true) ->inject('response') ->inject('dbForExternal') ->inject('database') @@ -482,8 +486,6 @@ App::post('/v1/database/collections/:collectionId/indexes') // lengths hidden by default $lengths = []; - var_dump($oldAttributes); - // set attribute size as length for strings, null otherwise foreach ($attributes as $key => $attribute) { // find attribute metadata in collection document @@ -686,8 +688,8 @@ App::post('/v1/database/collections/:collectionId/documents') ->label('sdk.response.model', Response::MODEL_DOCUMENT) ->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') ->param('data', [], new JSON(), 'Document data as JSON object.') - ->param('read', null, new ArrayList(new Text(64)), 'An array of strings with read permissions. By default only the current user is granted with read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true) - ->param('write', null, new ArrayList(new Text(64)), 'An array of strings with write permissions. By default only the current user is granted with write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true) + ->param('read', null, new Permissions(), 'An array of strings with read permissions. By default only the current user is granted with read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true) + ->param('write', null, new Permissions(), 'An array of strings with write permissions. By default only the current user is granted with write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true) ->inject('response') ->inject('dbForExternal') ->inject('user') @@ -767,6 +769,18 @@ App::get('/v1/database/collections/:collectionId/documents') return Query::parse($query); }, $queries); + // TODO@kodumbeats find a more efficient alternative to this + $schema = $collection->getArrayCopy()['attributes']; + $indexes = $collection->getArrayCopy()['indexes']; + $indexesInQueue = $collection->getArrayCopy()['indexesInQueue']; + + // TODO@kodumbeats use strict query validation + $validator = new QueriesValidator(new QueryValidator($schema), $indexes, $indexesInQueue, false); + + if (!$validator->isValid($queries)) { + throw new Exception($validator->getDescription(), 400); + } + $documents = $dbForExternal->find($collectionId, $queries, $limit, $offset, $orderAttributes, $orderTypes); $response->dynamic2(new Document([ @@ -824,8 +838,8 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId') ->param('collectionId', null, new UID(), 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') ->param('documentId', null, new UID(), 'Document unique ID.') ->param('data', [], new JSON(), 'Document data as JSON object.') - ->param('read', null, new ArrayList(new Text(64)), 'An array of strings with read permissions. By default inherits the existing read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true) - ->param('write', null, new ArrayList(new Text(64)), 'An array of strings with write permissions. By default inherits the existing write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true) + ->param('read', null, new Permissions(), 'An array of strings with read permissions. By default inherits the existing read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true) + ->param('write', null, new Permissions(), 'An array of strings with write permissions. By default inherits the existing write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.', true) ->inject('response') ->inject('dbForExternal') ->inject('audits') diff --git a/tests/e2e/Services/Database/DatabaseBase.php b/tests/e2e/Services/Database/DatabaseBase.php index f156dd71f..21f22ba7a 100644 --- a/tests/e2e/Services/Database/DatabaseBase.php +++ b/tests/e2e/Services/Database/DatabaseBase.php @@ -426,8 +426,8 @@ trait DatabaseBase 'releaseYear' => 2017, 'actors' => [], ], - 'read' => ['user:'.$this->getUser()['$id'], 'testx'], - 'write' => ['user:'.$this->getUser()['$id'], 'testy'], + 'read' => ['user:'.$this->getUser()['$id'], 'user:testx'], + 'write' => ['user:'.$this->getUser()['$id'], 'user:testy'], ]); $id = $document['body']['$id']; @@ -436,8 +436,8 @@ trait DatabaseBase $this->assertEquals($document['headers']['status-code'], 201); $this->assertEquals($document['body']['title'], 'Thor: Ragnaroc'); $this->assertEquals($document['body']['releaseYear'], 2017); - $this->assertEquals($document['body']['$read'][1], 'testx'); - $this->assertEquals($document['body']['$write'][1], 'testy'); + $this->assertEquals($document['body']['$read'][1], 'user:testx'); + $this->assertEquals($document['body']['$write'][1], 'user:testy'); $document = $this->client->call(Client::METHOD_PATCH, '/database/collections/' . $collection . '/documents/' . $id, array_merge([ 'content-type' => 'application/json', From acc4bbb052103aa4ac6f26a761ce4617683a6e9c Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 12 Jul 2021 12:45:30 -0400 Subject: [PATCH 74/90] Deletes worker no longer needed for collections --- app/controllers/api/database.php | 6 ------ app/workers/deletes.php | 13 ------------- 2 files changed, 19 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index f47f764a5..2dc9d149d 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -211,12 +211,6 @@ App::delete('/v1/database/collections/:collectionId') $dbForExternal->deleteCollection($collectionId); - // TODO@kodumbeats use worker to handle this - // $deletes - // ->setParam('type', DELETE_TYPE_DOCUMENT) - // ->setParam('document', $collection) - // ; - $events ->setParam('eventData', $response->output2($collection, Response::MODEL_COLLECTION)) ; diff --git a/app/workers/deletes.php b/app/workers/deletes.php index c11271e47..e45fe582f 100644 --- a/app/workers/deletes.php +++ b/app/workers/deletes.php @@ -52,9 +52,6 @@ class DeletesV1 extends Worker case Database::SYSTEM_COLLECTION_USERS: $this->deleteUser($document, $projectId); break; - case Database::SYSTEM_COLLECTION_COLLECTIONS: - $this->deleteDocuments($document, $projectId); - break; case Database::SYSTEM_COLLECTION_TEAMS: $this->deleteMemberships($document, $projectId); break; @@ -91,16 +88,6 @@ class DeletesV1 extends Worker { } - protected function deleteDocuments(Document $document, $projectId) - { - $collectionId = $document->getId(); - - // Delete Documents in the deleted collection - $this->deleteByGroup([ - '$collection='.$collectionId - ], $this->getProjectDB($projectId)); - } - protected function deleteMemberships(Document $document, $projectId) { // Delete Memberships $this->deleteByGroup([ From f869e6912391a3a45ccef4658ddd267f31568a01 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Mon, 12 Jul 2021 17:57:37 -0400 Subject: [PATCH 75/90] Delete user documents from new db --- app/workers/deletes.php | 94 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 90 insertions(+), 4 deletions(-) diff --git a/app/workers/deletes.php b/app/workers/deletes.php index e45fe582f..49f313e33 100644 --- a/app/workers/deletes.php +++ b/app/workers/deletes.php @@ -2,6 +2,8 @@ use Appwrite\Database\Database; use Utopia\Database\Database as Database2; +use Utopia\Database\Document as Document2; +use Utopia\Database\Query; use Utopia\Cache\Adapter\Redis as RedisCache; use Appwrite\Database\Adapter\MySQL as MySQLAdapter; use Appwrite\Database\Adapter\Redis as RedisAdapter; @@ -50,7 +52,7 @@ class DeletesV1 extends Worker $this->deleteFunction($document, $projectId); break; case Database::SYSTEM_COLLECTION_USERS: - $this->deleteUser($document, $projectId); + $this->deleteUser2($document, $projectId); break; case Database::SYSTEM_COLLECTION_TEAMS: $this->deleteMemberships($document, $projectId); @@ -144,6 +146,28 @@ class DeletesV1 extends Worker }); } + protected function deleteUser2(Document $document, $projectId) + { + $userId = $document->getId(); + + // Tokens and Sessions removed with user document + // Delete Memberships and decrement team membership counts + $this->deleteByGroup2('memberships', [ + new Query('userId', Query::TYPE_EQUAL, [$userId]) + ], $this->getInternalDB($projectId), function(Document $document) use ($projectId, $userId) { + + if ($document->getAttribute('confirm')) { // Count only confirmed members + $teamId = $document->getAttribute('teamId'); + $team = $this->getInternalDB($projectId)->getDocument('teams', $teamId); + if(!$team->isEmpty()) { + $team = $this->getInternalDB($projectId)->updateDocument('teams', $teamId, new Document2(\array_merge($team->getArrayCopy(), [ + 'sum' => \max($team->getAttribute('sum', 0) - 1, 0), // Ensure that sum >= 0 + ]))); + } + } + }); + } + protected function deleteExecutionLogs($timestamp) { $this->deleteForProjectIds(function($projectId) use ($timestamp) { @@ -161,7 +185,6 @@ class DeletesV1 extends Worker protected function deleteAbuseLogs($timestamp) { - global $register; if($timestamp == 0) { throw new Exception('Failed to delete audit logs. No timestamp provided'); } @@ -179,11 +202,10 @@ class DeletesV1 extends Worker protected function deleteAuditLogs($timestamp) { - global $register; if($timestamp == 0) { throw new Exception('Failed to delete audit logs. No timestamp provided'); } - $this->deleteForProjectIds(function($projectId) use ($register, $timestamp){ + $this->deleteForProjectIds(function($projectId) use ($timestamp){ $audit = new Audit($this->getInternalDB($projectId)); $status = $audit->cleanup($timestamp); if (!$status) { @@ -239,6 +261,30 @@ class DeletesV1 extends Worker Authorization::reset(); } + protected function deleteById2(Document2 $document, Database2 $database, callable $callback = null): bool + { + // TODO@kodumbeats this doesnt seem to work - getting the following error: + // "Write scopes ['role:all'] given, only ["user:{$userId}", "team:{$teamId}/owner"] allowed + 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'); + + if(is_callable($callback)) { + $callback($document); + } + + return true; + } + else { + Console::error('Failed to delete document: '.$document->getId()); + return false; + } + + Authorization::reset(); + } + protected function deleteForProjectIds(callable $callback) { $count = 0; @@ -320,6 +366,46 @@ class DeletesV1 extends Worker Console::info("Deleted {$count} document by group in " . ($executionEnd - $executionStart) . " seconds"); } + /** + * @param string $collection collectionID + * @param Query[] $queries + * @param Database2 $database + * @param callable $callback + */ + protected function deleteByGroup2(string $collection, array $queries, Database2 $database, callable $callback = null) + { + $count = 0; + $chunk = 0; + $limit = 50; + $results = []; + $sum = $limit; + + $executionStart = \microtime(true); + + while($sum === $limit) { + $chunk++; + + Authorization::disable(); + + $results = $database->find($collection, $queries, $limit, 0); + + Authorization::reset(); + + $sum = count($results); + + Console::info('Deleting chunk #'.$chunk.'. Found '.$sum.' documents'); + + foreach ($results as $document) { + $this->deleteById2($document, $database, $callback); + $count++; + } + } + + $executionEnd = \microtime(true); + + Console::info("Deleted {$count} document by group in " . ($executionEnd - $executionStart) . " seconds"); + } + protected function deleteCertificates(Document $document) { $domain = $document->getAttribute('domain'); From adb26c5356fcafeeb41e65d0dd7d961d570f1b6c Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Tue, 13 Jul 2021 09:41:15 -0400 Subject: [PATCH 76/90] Use Utopia authorization validator --- app/workers/deletes.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/workers/deletes.php b/app/workers/deletes.php index 49f313e33..316525467 100644 --- a/app/workers/deletes.php +++ b/app/workers/deletes.php @@ -9,6 +9,7 @@ use Appwrite\Database\Adapter\MySQL as MySQLAdapter; use Appwrite\Database\Adapter\Redis as RedisAdapter; use Appwrite\Database\Document; use Appwrite\Database\Validator\Authorization; +use Utopia\Database\Validator\Authorization as Authorization2; use Appwrite\Resque\Worker; use Utopia\Storage\Device\Local; use Utopia\Abuse\Abuse; @@ -154,7 +155,7 @@ class DeletesV1 extends Worker // Delete Memberships and decrement team membership counts $this->deleteByGroup2('memberships', [ new Query('userId', Query::TYPE_EQUAL, [$userId]) - ], $this->getInternalDB($projectId), function(Document $document) use ($projectId, $userId) { + ], $this->getInternalDB($projectId), function(Document2 $document) use ($projectId, $userId) { if ($document->getAttribute('confirm')) { // Count only confirmed members $teamId = $document->getAttribute('teamId'); @@ -265,7 +266,7 @@ class DeletesV1 extends Worker { // TODO@kodumbeats this doesnt seem to work - getting the following error: // "Write scopes ['role:all'] given, only ["user:{$userId}", "team:{$teamId}/owner"] allowed - Authorization::disable(); + Authorization2::disable(); // TODO@kodumbeats is it better to pass objects or ID strings? if($database->deleteDocument($document->getCollection(), $document->getId())) { @@ -282,7 +283,7 @@ class DeletesV1 extends Worker return false; } - Authorization::reset(); + Authorization2::reset(); } protected function deleteForProjectIds(callable $callback) @@ -385,11 +386,11 @@ class DeletesV1 extends Worker while($sum === $limit) { $chunk++; - Authorization::disable(); + Authorization2::disable(); $results = $database->find($collection, $queries, $limit, 0); - Authorization::reset(); + Authorization2::reset(); $sum = count($results); From 406271561312d49e53d27eb3f50496fe7983e628 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Tue, 13 Jul 2021 12:08:52 -0400 Subject: [PATCH 77/90] Handle deleting team memberships with new db --- app/workers/deletes.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/workers/deletes.php b/app/workers/deletes.php index 316525467..d391bd7e5 100644 --- a/app/workers/deletes.php +++ b/app/workers/deletes.php @@ -56,7 +56,7 @@ class DeletesV1 extends Worker $this->deleteUser2($document, $projectId); break; case Database::SYSTEM_COLLECTION_TEAMS: - $this->deleteMemberships($document, $projectId); + $this->deleteMemberships2($document, $projectId); break; default: Console::error('No lazy delete operation available for document of type: '.$document->getCollection()); @@ -99,6 +99,16 @@ class DeletesV1 extends Worker ], $this->getProjectDB($projectId)); } + // TODO@kodumbeats typehint Utopia\Database\Document $document + protected function deleteMemberships2($document, $projectId) { + $teamId = $document->getAttribute('teamId', ''); + + // Delete Memberships + $this->deleteByGroup2('memberships', [ + new Query('teamId', Query::TYPE_EQUAL, [$teamId]) + ], $this->getInternalDB($projectId)); + } + protected function deleteProject(Document $document) { // Delete all DBs From b58e773568f993c2e3ba909c2e5a8e620863b932 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Tue, 13 Jul 2021 12:10:20 -0400 Subject: [PATCH 78/90] Handle deleting execution logs with new db --- app/workers/deletes.php | 64 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/app/workers/deletes.php b/app/workers/deletes.php index d391bd7e5..3c1b75ba3 100644 --- a/app/workers/deletes.php +++ b/app/workers/deletes.php @@ -65,7 +65,7 @@ class DeletesV1 extends Worker break; case DELETE_TYPE_EXECUTIONS: - $this->deleteExecutionLogs($this->args['timestamp']); + $this->deleteExecutionLogs2($this->args['timestamp']); break; case DELETE_TYPE_AUDIT: @@ -194,6 +194,20 @@ class DeletesV1 extends Worker }); } + protected function deleteExecutionLogs2($timestamp) + { + $this->deleteForProjectIds2(function($projectId) use ($timestamp) { + if (!($dbForInternal = $this->getInternalDB($projectId))) { + throw new Exception('Failed to get projectDB for project '.$projectId); + } + + // Delete Executions + $this->deleteByGroup2('executions', [ + new Query('dateCreated', Query::TYPE_LESSER, [$timestamp]) + ], $dbForInternal); + }); + } + protected function deleteAbuseLogs($timestamp) { if($timestamp == 0) { @@ -337,6 +351,40 @@ class DeletesV1 extends Worker Console::info("Found {$count} projects " . ($executionEnd - $executionStart) . " seconds"); } + protected function deleteForProjectIds2(callable $callback) + { + $count = 0; + $chunk = 0; + $limit = 50; + $projects = []; + $sum = $limit; + + $executionStart = \microtime(true); + + while($sum === $limit) { + $chunk++; + + Authorization2::disable(); + $projects = $this->getConsoleDB2()->find('projects', [], $limit); + Authorization2::reset(); + + $projectIds = array_map (function ($project) { + return $project->getId(); + }, $projects); + + $sum = count($projects); + + Console::info('Executing delete function for chunk #'.$chunk.'. Found '.$sum.' projects'); + foreach ($projectIds as $projectId) { + $callback($projectId); + $count++; + } + } + + $executionEnd = \microtime(true); + Console::info("Found {$count} projects " . ($executionEnd - $executionStart) . " seconds"); + } + protected function deleteByGroup(array $filters, Database $database, callable $callback = null) { $count = 0; @@ -483,4 +531,18 @@ class DeletesV1 extends Worker return $dbForInternal; } + + /** + * @return Database2 + */ + protected function getConsoleDB2(): Database2 + { + global $register; + + $cache = new Cache(new RedisCache($register->get('cache'))); + $dbForConsole = new Database2(new MariaDB($register->get('db')), $cache); + $dbForConsole->setNamespace('project_console_internal'); // Main DB + + return $dbForConsole; + } } From 0af70aae30db4be912e802afdb9cc2125cd99031 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Tue, 13 Jul 2021 12:10:42 -0400 Subject: [PATCH 79/90] Handle deleting functions with new db --- app/workers/deletes.php | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/app/workers/deletes.php b/app/workers/deletes.php index 3c1b75ba3..1249db57b 100644 --- a/app/workers/deletes.php +++ b/app/workers/deletes.php @@ -50,7 +50,7 @@ class DeletesV1 extends Worker $this->deleteProject($document); break; case Database::SYSTEM_COLLECTION_FUNCTIONS: - $this->deleteFunction($document, $projectId); + $this->deleteFunction2($document, $projectId); break; case Database::SYSTEM_COLLECTION_USERS: $this->deleteUser2($document, $projectId); @@ -265,6 +265,31 @@ class DeletesV1 extends Worker ], $projectDB); } + // TODO@kodumbeats typehint Utopia\Database\Document $document + protected function deleteFunction2($document, $projectId) + { + $dbForInternal = $this->getInternalDB($projectId); + $device = new Local(APP_STORAGE_FUNCTIONS.'/app-'.$projectId); + + // Delete Tags + $this->deleteByGroup2('tags', [ + new Query('functionId', Query::TYPE_EQUAL, [$document->getId()]) + ], $dbForInternal, function(Document2 $document) use ($device) { + + if ($device->delete($document->getAttribute('path', ''))) { + Console::success('Delete code tag: '.$document->getAttribute('path', '')); + } + else { + Console::error('Failed to delete code tag: '.$document->getAttribute('path', '')); + } + }); + + // Delete Executions + $this->deleteByGroup2('executions', [ + new Query('functionId', Query::TYPE_EQUAL, [$document->getId()]) + ], $dbForInternal); + } + protected function deleteById(Document $document, Database $database, callable $callback = null): bool { Authorization::disable(); From 2db245a607cc443c7498d633d329bfeb30f5f996 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Tue, 13 Jul 2021 14:44:45 -0400 Subject: [PATCH 80/90] Use new db for deletes worker --- app/workers/deletes.php | 47 +++++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/app/workers/deletes.php b/app/workers/deletes.php index 1249db57b..89a0fbbd0 100644 --- a/app/workers/deletes.php +++ b/app/workers/deletes.php @@ -43,11 +43,11 @@ class DeletesV1 extends Worker switch (strval($type)) { case DELETE_TYPE_DOCUMENT: $document = $this->args['document'] ?? ''; - $document = new Document($document); + $document = new Document2($document); switch ($document->getCollection()) { case Database::SYSTEM_COLLECTION_PROJECTS: - $this->deleteProject($document); + $this->deleteProject2($document); break; case Database::SYSTEM_COLLECTION_FUNCTIONS: $this->deleteFunction2($document, $projectId); @@ -77,7 +77,7 @@ class DeletesV1 extends Worker break; case DELETE_TYPE_CERTIFICATES: - $document = new Document($this->args['document']); + $document = new Document2($this->args['document']); $this->deleteCertificates($document); break; @@ -99,8 +99,7 @@ class DeletesV1 extends Worker ], $this->getProjectDB($projectId)); } - // TODO@kodumbeats typehint Utopia\Database\Document $document - protected function deleteMemberships2($document, $projectId) { + protected function deleteMemberships2(Document2 $document, $projectId) { $teamId = $document->getAttribute('teamId', ''); // Delete Memberships @@ -121,6 +120,21 @@ class DeletesV1 extends Worker $cache->delete($cache->getRoot(), true); } + protected function deleteProject2(Document2 $document) + { + $projectId = $document->getId(); + // Delete all DBs + $this->getExternalDB($projectId)->delete(); + $this->getInternalDB($projectId)->delete(); + + // Delete all storage directories + $uploads = new Local(APP_STORAGE_UPLOADS.'/app-'.$document->getId()); + $cache = new Local(APP_STORAGE_CACHE.'/app-'.$document->getId()); + + $uploads->delete($uploads->getRoot(), true); + $cache->delete($cache->getRoot(), true); + } + protected function deleteUser(Document $document, $projectId) { $tokens = $document->getAttribute('tokens', []); @@ -157,7 +171,7 @@ class DeletesV1 extends Worker }); } - protected function deleteUser2(Document $document, $projectId) + protected function deleteUser2(Document2 $document, $projectId) { $userId = $document->getId(); @@ -265,8 +279,7 @@ class DeletesV1 extends Worker ], $projectDB); } - // TODO@kodumbeats typehint Utopia\Database\Document $document - protected function deleteFunction2($document, $projectId) + protected function deleteFunction2(Document2 $document, $projectId) { $dbForInternal = $this->getInternalDB($projectId); $device = new Local(APP_STORAGE_FUNCTIONS.'/app-'.$projectId); @@ -313,8 +326,6 @@ class DeletesV1 extends Worker protected function deleteById2(Document2 $document, Database2 $database, callable $callback = null): bool { - // TODO@kodumbeats this doesnt seem to work - getting the following error: - // "Write scopes ['role:all'] given, only ["user:{$userId}", "team:{$teamId}/owner"] allowed Authorization2::disable(); // TODO@kodumbeats is it better to pass objects or ID strings? @@ -490,7 +501,7 @@ class DeletesV1 extends Worker Console::info("Deleted {$count} document by group in " . ($executionEnd - $executionStart) . " seconds"); } - protected function deleteCertificates(Document $document) + protected function deleteCertificates(Document2 $document) { $domain = $document->getAttribute('domain'); $directory = APP_STORAGE_CERTIFICATES . '/' . $domain; @@ -557,6 +568,20 @@ class DeletesV1 extends Worker return $dbForInternal; } + /** + * @return Database2 + */ + protected function getExternalDB($projectId): Database2 + { + global $register; + + $cache = new Cache(new RedisCache($register->get('cache'))); + $dbForExternal = new Database2(new MariaDB($register->get('db')), $cache); + $dbForExternal->setNamespace('project_'.$projectId.'_external'); // Main DB + + return $dbForExternal; + } + /** * @return Database2 */ From 3d2888ac703466a441a9a876afeca2a9d2dcd9ad Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Tue, 13 Jul 2021 15:24:52 -0400 Subject: [PATCH 81/90] Remove unused functions --- app/workers/deletes.php | 368 +++++++++------------------------------- 1 file changed, 84 insertions(+), 284 deletions(-) diff --git a/app/workers/deletes.php b/app/workers/deletes.php index 89a0fbbd0..1242d7a8a 100644 --- a/app/workers/deletes.php +++ b/app/workers/deletes.php @@ -1,15 +1,10 @@ args['document'] ?? ''; - $document = new Document2($document); + $document = new Document($document); switch ($document->getCollection()) { - case Database::SYSTEM_COLLECTION_PROJECTS: - $this->deleteProject2($document); + // TODO@kodumbeats define these as constants somewhere + case 'projects': + $this->deleteProject($document); break; - case Database::SYSTEM_COLLECTION_FUNCTIONS: - $this->deleteFunction2($document, $projectId); + case 'functions': + $this->deleteFunction($document, $projectId); break; - case Database::SYSTEM_COLLECTION_USERS: - $this->deleteUser2($document, $projectId); + case 'users': + $this->deleteUser($document, $projectId); break; - case Database::SYSTEM_COLLECTION_TEAMS: - $this->deleteMemberships2($document, $projectId); + case 'teams': + $this->deleteMemberships($document, $projectId); break; default: Console::error('No lazy delete operation available for document of type: '.$document->getCollection()); @@ -65,7 +61,7 @@ class DeletesV1 extends Worker break; case DELETE_TYPE_EXECUTIONS: - $this->deleteExecutionLogs2($this->args['timestamp']); + $this->deleteExecutionLogs($this->args['timestamp']); break; case DELETE_TYPE_AUDIT: @@ -77,7 +73,7 @@ class DeletesV1 extends Worker break; case DELETE_TYPE_CERTIFICATES: - $document = new Document2($this->args['document']); + $document = new Document($this->args['document']); $this->deleteCertificates($document); break; @@ -91,36 +87,23 @@ class DeletesV1 extends Worker { } + /** + * @param Document $document teams document + * @param string $projectId + */ protected function deleteMemberships(Document $document, $projectId) { - // Delete Memberships - $this->deleteByGroup([ - '$collection='.Database::SYSTEM_COLLECTION_MEMBERSHIPS, - 'teamId='.$document->getId(), - ], $this->getProjectDB($projectId)); - } - - protected function deleteMemberships2(Document2 $document, $projectId) { $teamId = $document->getAttribute('teamId', ''); // Delete Memberships - $this->deleteByGroup2('memberships', [ + $this->deleteByGroup('memberships', [ new Query('teamId', Query::TYPE_EQUAL, [$teamId]) ], $this->getInternalDB($projectId)); } + /** + * @param Document $document project document + */ protected function deleteProject(Document $document) - { - // Delete all DBs - $this->getConsoleDB()->deleteNamespace($document->getId()); - $uploads = new Local(APP_STORAGE_UPLOADS.'/app-'.$document->getId()); - $cache = new Local(APP_STORAGE_CACHE.'/app-'.$document->getId()); - - // Delete all storage directories - $uploads->delete($uploads->getRoot(), true); - $cache->delete($cache->getRoot(), true); - } - - protected function deleteProject2(Document2 $document) { $projectId = $document->getId(); // Delete all DBs @@ -135,57 +118,25 @@ class DeletesV1 extends Worker $cache->delete($cache->getRoot(), true); } + /** + * @param Document $document user document + * @param string $projectId + */ protected function deleteUser(Document $document, $projectId) - { - $tokens = $document->getAttribute('tokens', []); - - foreach ($tokens as $token) { - if (!$this->getProjectDB($projectId)->deleteDocument($token->getId())) { - throw new Exception('Failed to remove token from DB'); - } - } - - $sessions = $document->getAttribute('sessions', []); - - foreach ($sessions as $session) { - if (!$this->getProjectDB($projectId)->deleteDocument($session->getId())) { - throw new Exception('Failed to remove session from DB'); - } - } - - // Delete Memberships and decrement team membership counts - $this->deleteByGroup([ - '$collection='.Database::SYSTEM_COLLECTION_MEMBERSHIPS, - 'userId='.$document->getId(), - ], $this->getProjectDB($projectId), function(Document $document) use ($projectId) { - - if ($document->getAttribute('confirm')) { // Count only confirmed members - $teamId = $document->getAttribute('teamId'); - $team = $this->getProjectDB($projectId)->getDocument($teamId); - if(!$team->isEmpty()) { - $team = $this->getProjectDB($projectId)->updateDocument(\array_merge($team->getArrayCopy(), [ - 'sum' => \max($team->getAttribute('sum', 0) - 1, 0), // Ensure that sum >= 0 - ])); - } - } - }); - } - - protected function deleteUser2(Document2 $document, $projectId) { $userId = $document->getId(); // Tokens and Sessions removed with user document // Delete Memberships and decrement team membership counts - $this->deleteByGroup2('memberships', [ + $this->deleteByGroup('memberships', [ new Query('userId', Query::TYPE_EQUAL, [$userId]) - ], $this->getInternalDB($projectId), function(Document2 $document) use ($projectId, $userId) { + ], $this->getInternalDB($projectId), function(Document $document) use ($projectId, $userId) { if ($document->getAttribute('confirm')) { // Count only confirmed members $teamId = $document->getAttribute('teamId'); $team = $this->getInternalDB($projectId)->getDocument('teams', $teamId); if(!$team->isEmpty()) { - $team = $this->getInternalDB($projectId)->updateDocument('teams', $teamId, new Document2(\array_merge($team->getArrayCopy(), [ + $team = $this->getInternalDB($projectId)->updateDocument('teams', $teamId, new Document(\array_merge($team->getArrayCopy(), [ 'sum' => \max($team->getAttribute('sum', 0) - 1, 0), // Ensure that sum >= 0 ]))); } @@ -193,35 +144,26 @@ class DeletesV1 extends Worker }); } + /** + * @param int $timestamp + */ protected function deleteExecutionLogs($timestamp) { $this->deleteForProjectIds(function($projectId) use ($timestamp) { - if (!($projectDB = $this->getProjectDB($projectId))) { - throw new Exception('Failed to get projectDB for project '.$projectId); - } - - // Delete Executions - $this->deleteByGroup([ - '$collection='.Database::SYSTEM_COLLECTION_EXECUTIONS, - 'dateCreated<'.$timestamp - ], $projectDB); - }); - } - - protected function deleteExecutionLogs2($timestamp) - { - $this->deleteForProjectIds2(function($projectId) use ($timestamp) { if (!($dbForInternal = $this->getInternalDB($projectId))) { throw new Exception('Failed to get projectDB for project '.$projectId); } // Delete Executions - $this->deleteByGroup2('executions', [ + $this->deleteByGroup('executions', [ new Query('dateCreated', Query::TYPE_LESSER, [$timestamp]) ], $dbForInternal); }); } + /** + * @param int $timestamp + */ protected function deleteAbuseLogs($timestamp) { if($timestamp == 0) { @@ -239,6 +181,9 @@ class DeletesV1 extends Worker }); } + /** + * @param int $timestamp + */ protected function deleteAuditLogs($timestamp) { if($timestamp == 0) { @@ -253,41 +198,19 @@ class DeletesV1 extends Worker }); } + /** + * @param Document $document function document + * @param string $projectId + */ protected function deleteFunction(Document $document, $projectId) - { - $projectDB = $this->getProjectDB($projectId); - $device = new Local(APP_STORAGE_FUNCTIONS.'/app-'.$projectId); - - // Delete Tags - $this->deleteByGroup([ - '$collection='.Database::SYSTEM_COLLECTION_TAGS, - 'functionId='.$document->getId(), - ], $projectDB, function(Document $document) use ($device) { - - if ($device->delete($document->getAttribute('path', ''))) { - Console::success('Delete code tag: '.$document->getAttribute('path', '')); - } - else { - Console::error('Failed to delete code tag: '.$document->getAttribute('path', '')); - } - }); - - // Delete Executions - $this->deleteByGroup([ - '$collection='.Database::SYSTEM_COLLECTION_EXECUTIONS, - 'functionId='.$document->getId(), - ], $projectDB); - } - - protected function deleteFunction2(Document2 $document, $projectId) { $dbForInternal = $this->getInternalDB($projectId); $device = new Local(APP_STORAGE_FUNCTIONS.'/app-'.$projectId); // Delete Tags - $this->deleteByGroup2('tags', [ + $this->deleteByGroup('tags', [ new Query('functionId', Query::TYPE_EQUAL, [$document->getId()]) - ], $dbForInternal, function(Document2 $document) use ($device) { + ], $dbForInternal, function(Document $document) use ($device) { if ($device->delete($document->getAttribute('path', ''))) { Console::success('Delete code tag: '.$document->getAttribute('path', '')); @@ -298,36 +221,23 @@ class DeletesV1 extends Worker }); // Delete Executions - $this->deleteByGroup2('executions', [ + $this->deleteByGroup('executions', [ new Query('functionId', Query::TYPE_EQUAL, [$document->getId()]) ], $dbForInternal); } + + /** + * @param Document $document to be deleted + * @param Database $database to delete it from + * @param callable $callback to perform after document is deleted + * + * @return bool + */ protected function deleteById(Document $document, Database $database, callable $callback = null): bool { Authorization::disable(); - if($database->deleteDocument($document->getId())) { - Console::success('Deleted document "'.$document->getId().'" successfully'); - - if(is_callable($callback)) { - $callback($document); - } - - return true; - } - else { - Console::error('Failed to delete document: '.$document->getId()); - return false; - } - - Authorization::reset(); - } - - protected function deleteById2(Document2 $document, Database2 $database, callable $callback = null): bool - { - Authorization2::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'); @@ -343,9 +253,12 @@ class DeletesV1 extends Worker return false; } - Authorization2::reset(); + Authorization::reset(); } + /** + * @param callable $callback + */ protected function deleteForProjectIds(callable $callback) { $count = 0; @@ -355,19 +268,12 @@ class DeletesV1 extends Worker $sum = $limit; $executionStart = \microtime(true); - + while($sum === $limit) { $chunk++; Authorization::disable(); - $projects = $this->getConsoleDB()->getCollection([ - 'limit' => $limit, - 'orderType' => 'ASC', - 'orderCast' => 'string', - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_PROJECTS, - ], - ]); + $projects = $this->getConsoleDB()->find('projects', [], $limit); Authorization::reset(); $projectIds = array_map (function ($project) { @@ -387,41 +293,13 @@ class DeletesV1 extends Worker Console::info("Found {$count} projects " . ($executionEnd - $executionStart) . " seconds"); } - protected function deleteForProjectIds2(callable $callback) - { - $count = 0; - $chunk = 0; - $limit = 50; - $projects = []; - $sum = $limit; - - $executionStart = \microtime(true); - - while($sum === $limit) { - $chunk++; - - Authorization2::disable(); - $projects = $this->getConsoleDB2()->find('projects', [], $limit); - Authorization2::reset(); - - $projectIds = array_map (function ($project) { - return $project->getId(); - }, $projects); - - $sum = count($projects); - - Console::info('Executing delete function for chunk #'.$chunk.'. Found '.$sum.' projects'); - foreach ($projectIds as $projectId) { - $callback($projectId); - $count++; - } - } - - $executionEnd = \microtime(true); - Console::info("Found {$count} projects " . ($executionEnd - $executionStart) . " seconds"); - } - - protected function deleteByGroup(array $filters, Database $database, callable $callback = null) + /** + * @param string $collection collectionID + * @param Query[] $queries + * @param Database $database + * @param callable $callback + */ + protected function deleteByGroup(string $collection, array $queries, Database $database, callable $callback = null) { $count = 0; $chunk = 0; @@ -430,19 +308,13 @@ class DeletesV1 extends Worker $sum = $limit; $executionStart = \microtime(true); - + while($sum === $limit) { $chunk++; Authorization::disable(); - $results = $database->getCollection([ - 'limit' => $limit, - 'orderField' => '$id', - 'orderType' => 'ASC', - 'orderCast' => 'string', - 'filters' => $filters, - ]); + $results = $database->find($collection, $queries, $limit, 0); Authorization::reset(); @@ -462,46 +334,10 @@ class DeletesV1 extends Worker } /** - * @param string $collection collectionID - * @param Query[] $queries - * @param Database2 $database - * @param callable $callback + * @param Document $document certificates document + * @return Database */ - protected function deleteByGroup2(string $collection, array $queries, Database2 $database, callable $callback = null) - { - $count = 0; - $chunk = 0; - $limit = 50; - $results = []; - $sum = $limit; - - $executionStart = \microtime(true); - - while($sum === $limit) { - $chunk++; - - Authorization2::disable(); - - $results = $database->find($collection, $queries, $limit, 0); - - Authorization2::reset(); - - $sum = count($results); - - Console::info('Deleting chunk #'.$chunk.'. Found '.$sum.' documents'); - - foreach ($results as $document) { - $this->deleteById2($document, $database, $callback); - $count++; - } - } - - $executionEnd = \microtime(true); - - Console::info("Deleted {$count} document by group in " . ($executionEnd - $executionStart) . " seconds"); - } - - protected function deleteCertificates(Document2 $document) + protected function deleteCertificates(Document $document) { $domain = $document->getAttribute('domain'); $directory = APP_STORAGE_CERTIFICATES . '/' . $domain; @@ -515,82 +351,46 @@ class DeletesV1 extends Worker Console::info("No certificate files found for {$domain}"); } } - - /** - * @return Database; - */ - protected function getConsoleDB(): Database - { - global $register; - - $db = $register->get('db'); - $cache = $register->get('cache'); - - if($this->consoleDB === null) { - $this->consoleDB = new Database(); - $this->consoleDB->setAdapter(new RedisAdapter(new MySQLAdapter($db, $cache), $cache));; - $this->consoleDB->setNamespace('app_console'); // Main DB - $this->consoleDB->setMocks(Config::getParam('collections', [])); - } - - return $this->consoleDB; - } - - /** - * @return Database; - */ - protected function getProjectDB($projectId): Database - { - global $register; - - $db = $register->get('db'); - $cache = $register->get('cache'); - - $projectDB = new Database(); - $projectDB->setAdapter(new RedisAdapter(new MySQLAdapter($db, $cache), $cache)); - $projectDB->setNamespace('app_'.$projectId); // Main DB - $projectDB->setMocks(Config::getParam('collections', [])); - - return $projectDB; - } /** - * @return Database2 + * @param string $projectId + * @return Database */ - protected function getInternalDB($projectId): Database2 + protected function getInternalDB($projectId): Database { global $register; $cache = new Cache(new RedisCache($register->get('cache'))); - $dbForInternal = new Database2(new MariaDB($register->get('db')), $cache); + $dbForInternal = new Database(new MariaDB($register->get('db')), $cache); $dbForInternal->setNamespace('project_'.$projectId.'_internal'); // Main DB return $dbForInternal; } /** - * @return Database2 + * @param string $projectId + * @return Database */ - protected function getExternalDB($projectId): Database2 + protected function getExternalDB($projectId): Database { global $register; $cache = new Cache(new RedisCache($register->get('cache'))); - $dbForExternal = new Database2(new MariaDB($register->get('db')), $cache); + $dbForExternal = new Database(new MariaDB($register->get('db')), $cache); $dbForExternal->setNamespace('project_'.$projectId.'_external'); // Main DB return $dbForExternal; } /** - * @return Database2 + * @return Database */ - protected function getConsoleDB2(): Database2 + protected function getConsoleDB(): Database { global $register; $cache = new Cache(new RedisCache($register->get('cache'))); - $dbForConsole = new Database2(new MariaDB($register->get('db')), $cache); + $dbForConsole = new Database(new MariaDB($register->get('db')), $cache); $dbForConsole->setNamespace('project_console_internal'); // Main DB return $dbForConsole; From 30483610f81ac8b4a0dbc0581b9c4c8552a27c92 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Wed, 14 Jul 2021 15:09:48 -0400 Subject: [PATCH 82/90] Add missing typehint --- app/workers/database.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/workers/database.php b/app/workers/database.php index 49f19efdb..b02487104 100644 --- a/app/workers/database.php +++ b/app/workers/database.php @@ -165,6 +165,7 @@ class DatabaseV1 extends Worker { global $register; + /** @var Database $dbForExternal */ $dbForExternal = null; go(function() use ($register, $projectId, &$dbForExternal) { From 88146857f7502267c1fb6081c9182f186185ab07 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Wed, 14 Jul 2021 15:59:16 -0400 Subject: [PATCH 83/90] Use refactored db for tasks worker --- app/controllers/api/projects.php | 1 + app/workers/tasks.php | 53 ++++++++++++--------- src/Appwrite/Utopia/Response/Model/Task.php | 6 +++ 3 files changed, 37 insertions(+), 23 deletions(-) diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 7fc91ab1d..6b2591dca 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -1006,6 +1006,7 @@ App::post('/v1/projects/:projectId/tasks') $task = new Document([ '$id' => $dbForConsole->getId(), + '$projectId' => $project->getId(), 'name' => $name, 'status' => $status, 'schedule' => $schedule, diff --git a/app/workers/tasks.php b/app/workers/tasks.php index 0b1b33e23..287aea5fa 100644 --- a/app/workers/tasks.php +++ b/app/workers/tasks.php @@ -1,14 +1,14 @@ get('db'); $cache = $register->get('cache'); - $consoleDB = new Database(); - $consoleDB->setAdapter(new RedisAdapter(new MySQLAdapter($db, $cache), $cache)); - $consoleDB->setNamespace('app_console'); // Main DB - $consoleDB->setMocks(Config::getParam('collections', [])); + $projectId = $this->args['$projectId'] ?? null; + $taskId = $this->args['$id'] ?? null; + $updated = $this->args['updated'] ?? null; + $next = $this->args['next'] ?? null; + $delay = \time() - $next; + $errors = []; + $timeout = 60 * 5; // 5 minutes + $errorLimit = 5; + $logLimit = 5; + $alert = ''; + + $cache = new Cache(new Redis($cache)); + $dbForConsole = new Database(new MariaDB($db), $cache); + $dbForConsole->setNamespace('project_console_internal'); /* * 1. Get Original Task @@ -51,30 +61,25 @@ class TasksV1 extends Worker * If error count bigger than allowed change status to pause */ - $taskId = $this->args['$id'] ?? null; - $updated = $this->args['updated'] ?? null; - $next = $this->args['next'] ?? null; - $delay = \time() - $next; - $errors = []; - $timeout = 60 * 5; // 5 minutes - $errorLimit = 5; - $logLimit = 5; - $alert = ''; - if (empty($taskId)) { throw new Exception('Missing task $id'); } Authorization::disable(); - $task = $consoleDB->getDocument($taskId); + $project = $dbForConsole->getDocument('projects', $projectId); Authorization::reset(); - if (\is_null($task->getId()) || Database::SYSTEM_COLLECTION_TASKS !== $task->getCollection()) { + // Find the task in the $project->getAttribute('tasks') array + $taskIndex = array_search($taskId, array_column($project->getAttributes()['tasks'], '$id')); + + if ($taskIndex === false) { throw new Exception('Task Not Found'); } + $task = $project->getAttribute('tasks')[$taskIndex]; + if ($task->getAttribute('updated') !== $updated) { // Task have already been rescheduled by owner return; } @@ -193,9 +198,11 @@ class TasksV1 extends Worker ->setAttribute('delay', $delay) ; - Authorization::disable(); + $project->findAndReplace('$id', $task->getId(), $task); - if (false === $consoleDB->updateDocument($task->getArrayCopy())) { + Authorization::disable(); + + if (false === $dbForConsole->updateDocument('projects', $project->getId(), $project)) { throw new Exception('Failed saving tasks to DB'); } diff --git a/src/Appwrite/Utopia/Response/Model/Task.php b/src/Appwrite/Utopia/Response/Model/Task.php index 10e97acad..974bd4baf 100644 --- a/src/Appwrite/Utopia/Response/Model/Task.php +++ b/src/Appwrite/Utopia/Response/Model/Task.php @@ -21,6 +21,12 @@ class Task extends Model 'default' => '', 'example' => '5e5ea5c16897e', ]) + ->addRule('$projectId', [ + 'type' => self::TYPE_STRING, + 'description' => 'Project ID.', + 'default' => '', + 'example' => '5e5ea5c16897e', + ]) ->addRule('name', [ 'type' => self::TYPE_STRING, 'description' => 'Task name.', From 0243d6c9e158ac921ab9b83d1950c43610e2ffc2 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Thu, 15 Jul 2021 17:14:52 -0400 Subject: [PATCH 84/90] Use refactored db for certificates worker --- app/config/collections2.php | 85 +++++++++ app/controllers/general.php | 39 ++-- app/workers/certificates.php | 333 +++++++++++++++++------------------ 3 files changed, 267 insertions(+), 190 deletions(-) diff --git a/app/config/collections2.php b/app/config/collections2.php index d1e5539dd..0a69b81c3 100644 --- a/app/config/collections2.php +++ b/app/config/collections2.php @@ -1047,6 +1047,91 @@ $collections = [ ] ], ], + 'certificates' => [ + '$collection' => Database::COLLECTIONS, + '$id' => 'certificates', + 'name' => 'Certificates', + 'attributes' => [, + [ + '$id' => 'domain', + 'type' => Database::VAR_STRING, + 'format' => '', + // The maximum total length of a domain name or number is 255 characters. + // https://datatracker.ietf.org/doc/html/rfc2821#section-4.5.3.1 + // https://datatracker.ietf.org/doc/html/rfc5321#section-4.5.3.1.2 + 'size' => 255, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'issueDate', + 'type' => Database::VAR_INTEGER, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'renewDate', + 'type' => Database::VAR_INTEGER, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'attempts', + 'type' => Database::VAR_INTEGER, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'log', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'updated', + 'type' => Database::VAR_INTEGER, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + ], + 'indexes' => [ + [ + '$id' => '_key_domain', + 'type' => Database::INDEX_KEY, + 'attributes' => ['domain'], + 'lengths' => [255], + 'orders' => [Database::ORDER_ASC], + ], + ], + ], ]; /* diff --git a/app/controllers/general.php b/app/controllers/general.php index e8392cde4..8b599a44c 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -16,17 +16,19 @@ use Appwrite\Utopia\Response\Filters\V06; use Appwrite\Utopia\Response\Filters\V07; use Appwrite\Utopia\Response\Filters\V08; use Utopia\CLI\Console; +use Utopia\Database\Database; use Utopia\Database\Document; +use Utopia\Database\Query; use Utopia\Database\Validator\Authorization as Authorization2; Config::setParam('domainVerification', false); Config::setParam('cookieDomain', 'localhost'); Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE); -App::init(function ($utopia, $request, $response, $console, $project, $consoleDB, $user, $locale, $clients) { +App::init(function ($utopia, $request, $response, $console, $project, $dbForConsole, $user, $locale, $clients) { /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ - /** @var Appwrite\Database\Database $consoleDB */ + /** @var Utopia\Database\Database $dbForConsole */ /** @var Utopia\Database\Document $console */ /** @var Utopia\Database\Document $project */ /** @var Utopia\Database\Document $user */ @@ -42,36 +44,29 @@ App::init(function ($utopia, $request, $response, $console, $project, $consoleDB $domains[$domain->get()] = false; Console::warning($domain->get() . ' is not a publicly accessible domain. Skipping SSL certificate generation.'); } else { - Authorization::disable(); - $dbDomain = $consoleDB->getCollectionFirst([ - 'limit' => 1, - 'offset' => 0, - 'filters' => [ - '$collection=' . Database::SYSTEM_COLLECTION_CERTIFICATES, - 'domain=' . $domain->get(), - ], - ]); + Authorization2::disable(); - if (empty($dbDomain)) { - $dbDomain = [ - '$collection' => Database::SYSTEM_COLLECTION_CERTIFICATES, - '$permissions' => [ - 'read' => [], - 'write' => [], - ], + $certificate = $dbForConsole->findFirst('certificates', [ + new Query('domain', QUERY::TYPE_EQUAL, [$domain->get()]) + ], /*limit*/ 1); + + if (empty($certificate)) { + $certificate = new Document([ 'domain' => $domain->get(), - ]; - $dbDomain = $consoleDB->createDocument($dbDomain); - Authorization::enable(); + ]); + $certificate = $dbForConsole->createDocument('certificates', $certificate); + Authorization2::enable(); Console::info('Issuing a TLS certificate for the master domain (' . $domain->get() . ') in a few seconds...'); // TODO move this to installation script Resque::enqueue('v1-certificates', 'CertificatesV1', [ - 'document' => $dbDomain, + 'document' => $certificate, 'domain' => $domain->get(), 'validateTarget' => false, 'validateCNAME' => false, ]); + } else { + Authorization2::enable(); // ensure authorization is reenabled } $domains[$domain->get()] = true; } diff --git a/app/workers/certificates.php b/app/workers/certificates.php index bc746775c..1db2a4636 100644 --- a/app/workers/certificates.php +++ b/app/workers/certificates.php @@ -1,14 +1,16 @@ get('db'); - $cache = $register->get('cache'); + go(function() use ($register) { + $db = $register->get('dbPool')->get(); + $redis = $register->get('redisPool')->get(); - $consoleDB = new Database(); - $consoleDB->setAdapter(new RedisAdapter(new MySQLAdapter($db, $cache), $cache)); - $consoleDB->setNamespace('app_console'); // Main DB - $consoleDB->setMocks(Config::getParam('collections', [])); + $cache = new Cache(new Redis($redis)); + $dbForConsole = new Database(new MariaDB($db), $cache); + $dbForConsole->setNamespace('project_console_internal'); - /** - * 1. Get new domain document - DONE - * 1.1. Validate domain is valid, public suffix is known and CNAME records are verified - DONE - * 2. Check if a certificate already exists - DONE - * 3. Check if certificate is about to expire, if not - skip it - * 3.1. Create / renew certificate - * 3.2. Update loadblancer - * 3.3. Update database (domains, change date, expiry) - * 3.4. Set retry on failure - * 3.5. Schedule to renew certificate in 60 days - */ + /** + * 1. Get new domain document - DONE + * 1.1. Validate domain is valid, public suffix is known and CNAME records are verified - DONE + * 2. Check if a certificate already exists - DONE + * 3. Check if certificate is about to expire, if not - skip it + * 3.1. Create / renew certificate + * 3.2. Update loadblancer + * 3.3. Update database (domains, change date, expiry) + * 3.4. Set retry on failure + * 3.5. Schedule to renew certificate in 60 days + */ - Authorization::disable(); + Authorization::disable(); - // Args - $document = $this->args['document']; - $domain = $this->args['domain']; + // Args + $document = $this->args['document']; + $domain = $this->args['domain']; - // Validation Args - $validateTarget = $this->args['validateTarget'] ?? true; - $validateCNAME = $this->args['validateCNAME'] ?? true; + // Validation Args + $validateTarget = $this->args['validateTarget'] ?? true; + $validateCNAME = $this->args['validateCNAME'] ?? true; + + // Options + $domain = new Domain((!empty($domain)) ? $domain : ''); + $expiry = 60 * 60 * 24 * 30 * 2; // 60 days + $safety = 60 * 60; // 1 hour + $renew = (\time() + $expiry); + + if(empty($domain->get())) { + throw new Exception('Missing domain'); + } + + if(!$domain->isKnown() || $domain->isTest()) { + throw new Exception('Unknown public suffix for domain'); + } + + if($validateTarget) { + $target = new Domain(App::getEnv('_APP_DOMAIN_TARGET', '')); - // Options - $domain = new Domain((!empty($domain)) ? $domain : ''); - $expiry = 60 * 60 * 24 * 30 * 2; // 60 days - $safety = 60 * 60; // 1 hour - $renew = (\time() + $expiry); - - if(empty($domain->get())) { - throw new Exception('Missing domain'); - } - - if(!$domain->isKnown() || $domain->isTest()) { - throw new Exception('Unknown public suffix for domain'); - } - - if($validateTarget) { - $target = new Domain(App::getEnv('_APP_DOMAIN_TARGET', '')); - - if(!$target->isKnown() || $target->isTest()) { - throw new Exception('Unreachable CNAME target ('.$target->get().'), please use a domain with a public suffix.'); + if(!$target->isKnown() || $target->isTest()) { + throw new Exception('Unreachable CNAME target ('.$target->get().'), please use a domain with a public suffix.'); + } } - } - if($validateCNAME) { - $validator = new CNAME($target->get()); // Verify Domain with DNS records - - if(!$validator->isValid($domain->get())) { - throw new Exception('Failed to verify domain DNS records'); - } - } - - $certificate = $consoleDB->getCollectionFirst([ - 'limit' => 1, - 'offset' => 0, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_CERTIFICATES, - 'domain='.$domain->get(), - ], - ]); - - // $condition = ($certificate - // && $certificate instanceof Document - // && isset($certificate['issueDate']) - // && (($certificate['issueDate'] + ($expiry)) > time())) ? 'true' : 'false'; - - // throw new Exception('cert issued at'.date('d.m.Y H:i', $certificate['issueDate']).' | renew date is: '.date('d.m.Y H:i', ($certificate['issueDate'] + ($expiry))).' | condition is '.$condition); - - $certificate = (!empty($certificate) && $certificate instanceof $certificate) ? $certificate->getArrayCopy() : []; - - if(!empty($certificate) - && isset($certificate['issueDate']) - && (($certificate['issueDate'] + ($expiry)) > \time())) { // Check last issue time - throw new Exception('Renew isn\'t required'); - } - - $staging = (App::isProduction()) ? '' : ' --dry-run'; - $email = App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS'); - - if(empty($email)) { - throw new Exception('You must set a valid security email address (_APP_SYSTEM_SECURITY_EMAIL_ADDRESS) to issue an SSL certificate'); - } - - $stdout = ''; - $stderr = ''; - - $exit = Console::execute("certbot certonly --webroot --noninteractive --agree-tos{$staging}" - ." --email ".$email - ." -w ".APP_STORAGE_CERTIFICATES - ." -d {$domain->get()}", '', $stdout, $stderr); - - if($exit !== 0) { - throw new Exception('Failed to issue a certificate with message: '.$stderr); - } - - $path = APP_STORAGE_CERTIFICATES.'/'.$domain->get(); - - if(!\is_readable($path)) { - if (!\mkdir($path, 0755, true)) { - throw new Exception('Failed to create path...'); - } - } + if($validateCNAME) { + $validator = new CNAME($target->get()); // Verify Domain with DNS records - if(!@\rename('/etc/letsencrypt/live/'.$domain->get().'/cert.pem', APP_STORAGE_CERTIFICATES.'/'.$domain->get().'/cert.pem')) { - throw new Exception('Failed to rename certificate cert.pem: '.\json_encode($stdout)); - } - - if(!@\rename('/etc/letsencrypt/live/'.$domain->get().'/chain.pem', APP_STORAGE_CERTIFICATES.'/'.$domain->get().'/chain.pem')) { - throw new Exception('Failed to rename certificate chain.pem: '.\json_encode($stdout)); - } - - if(!@\rename('/etc/letsencrypt/live/'.$domain->get().'/fullchain.pem', APP_STORAGE_CERTIFICATES.'/'.$domain->get().'/fullchain.pem')) { - throw new Exception('Failed to rename certificate fullchain.pem: '.\json_encode($stdout)); - } - - if(!@\rename('/etc/letsencrypt/live/'.$domain->get().'/privkey.pem', APP_STORAGE_CERTIFICATES.'/'.$domain->get().'/privkey.pem')) { - throw new Exception('Failed to rename certificate privkey.pem: '.\json_encode($stdout)); - } - - $certificate = \array_merge($certificate, [ - '$collection' => Database::SYSTEM_COLLECTION_CERTIFICATES, - '$permissions' => [ - 'read' => [], - 'write' => [], - ], - 'domain' => $domain->get(), - 'issueDate' => \time(), - 'renewDate' => $renew, - 'attempts' => 0, - 'log' => \json_encode($stdout), - ]); - - $certificate = $consoleDB->createDocument($certificate); - - if(!$certificate) { - throw new Exception('Failed saving certificate to DB'); - } - - if(!empty($document)) { - $document = \array_merge($document, [ - 'updated' => \time(), - 'certificateId' => $certificate->getId(), - ]); - - $document = $consoleDB->updateDocument($document); - - if(!$document) { - throw new Exception('Failed saving domain to DB'); + if(!$validator->isValid($domain->get())) { + throw new Exception('Failed to verify domain DNS records'); + } } - } + + $certificate = $dbForConsole->findFirst('certificates', [ + new Query('domain', QUERY::TYPE_EQUAL, [$domain->get()]) + ], /*limit*/ 1); + + // $condition = ($certificate + // && $certificate instanceof Document + // && isset($certificate['issueDate']) + // && (($certificate['issueDate'] + ($expiry)) > time())) ? 'true' : 'false'; + + // throw new Exception('cert issued at'.date('d.m.Y H:i', $certificate['issueDate']).' | renew date is: '.date('d.m.Y H:i', ($certificate['issueDate'] + ($expiry))).' | condition is '.$condition); + + $certificate = (!empty($certificate) && $certificate instanceof $certificate) ? $certificate->getArrayCopy() : []; + + if(!empty($certificate) + && isset($certificate['issueDate']) + && (($certificate['issueDate'] + ($expiry)) > \time())) { // Check last issue time + throw new Exception('Renew isn\'t required'); + } + + $staging = (App::isProduction()) ? '' : ' --dry-run'; + $email = App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS'); + + if(empty($email)) { + throw new Exception('You must set a valid security email address (_APP_SYSTEM_SECURITY_EMAIL_ADDRESS) to issue an SSL certificate'); + } + + $stdout = ''; + $stderr = ''; + + $exit = Console::execute("certbot certonly --webroot --noninteractive --agree-tos{$staging}" + ." --email ".$email + ." -w ".APP_STORAGE_CERTIFICATES + ." -d {$domain->get()}", '', $stdout, $stderr); + + if($exit !== 0) { + throw new Exception('Failed to issue a certificate with message: '.$stderr); + } + + $path = APP_STORAGE_CERTIFICATES.'/'.$domain->get(); + + if(!\is_readable($path)) { + if (!\mkdir($path, 0755, true)) { + throw new Exception('Failed to create path...'); + } + } + + if(!@\rename('/etc/letsencrypt/live/'.$domain->get().'/cert.pem', APP_STORAGE_CERTIFICATES.'/'.$domain->get().'/cert.pem')) { + throw new Exception('Failed to rename certificate cert.pem: '.\json_encode($stdout)); + } + + if(!@\rename('/etc/letsencrypt/live/'.$domain->get().'/chain.pem', APP_STORAGE_CERTIFICATES.'/'.$domain->get().'/chain.pem')) { + throw new Exception('Failed to rename certificate chain.pem: '.\json_encode($stdout)); + } + + if(!@\rename('/etc/letsencrypt/live/'.$domain->get().'/fullchain.pem', APP_STORAGE_CERTIFICATES.'/'.$domain->get().'/fullchain.pem')) { + throw new Exception('Failed to rename certificate fullchain.pem: '.\json_encode($stdout)); + } + + if(!@\rename('/etc/letsencrypt/live/'.$domain->get().'/privkey.pem', APP_STORAGE_CERTIFICATES.'/'.$domain->get().'/privkey.pem')) { + throw new Exception('Failed to rename certificate privkey.pem: '.\json_encode($stdout)); + } + + $certificate = new Document(\array_merge($certificate, [ + 'domain' => $domain->get(), + 'issueDate' => \time(), + 'renewDate' => $renew, + 'attempts' => 0, + 'log' => \json_encode($stdout), + ])); + + $certificate = $dbForConsole->createDocument('certificates', $certificate); + + if(!$certificate) { + throw new Exception('Failed saving certificate to DB'); + } + + if(!empty($document)) { + $certificate = new Document(\array_merge($document, [ + 'updated' => \time(), + 'certificateId' => $certificate->getId(), + ])); - $config = -"tls: - certificates: - - certFile: /storage/certificates/{$domain->get()}/fullchain.pem - keyFile: /storage/certificates/{$domain->get()}/privkey.pem"; + $certificate = $dbForConsole->updateDocument('certificates', $certificate->getId(), $certificate); + + if(!$certificate) { + throw new Exception('Failed saving domain to DB'); + } + } + + $config = + "tls: + certificates: + - certFile: /storage/certificates/{$domain->get()}/fullchain.pem + keyFile: /storage/certificates/{$domain->get()}/privkey.pem"; - if(!\file_put_contents(APP_STORAGE_CONFIG.'/'.$domain->get().'.yml', $config)) { - throw new Exception('Failed to save SSL configuration'); - } + if(!\file_put_contents(APP_STORAGE_CONFIG.'/'.$domain->get().'.yml', $config)) { + throw new Exception('Failed to save SSL configuration'); + } - ResqueScheduler::enqueueAt($renew + $safety, 'v1-certificates', 'CertificatesV1', [ - 'document' => [], - 'domain' => $domain->get(), - 'validateTarget' => $validateTarget, - 'validateCNAME' => $validateCNAME, - ]); // Async task rescheduale + ResqueScheduler::enqueueAt($renew + $safety, 'v1-certificates', 'CertificatesV1', [ + 'document' => [], + 'domain' => $domain->get(), + 'validateTarget' => $validateTarget, + 'validateCNAME' => $validateCNAME, + ]); // Async task rescheduale - Authorization::reset(); + Authorization::reset(); + + // Return db connections to pool + $register->get('dbPool')->put($db); + $register->get('redisPool')->put($redis); + }); } public function shutdown(): void From f749256e03304e7137b441cd1937f9b34f6bdd2e Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 16 Jul 2021 09:33:30 -0400 Subject: [PATCH 85/90] Fix typos in changes for certificates worker --- app/config/collections2.php | 2 +- app/controllers/general.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/config/collections2.php b/app/config/collections2.php index 0a69b81c3..2e0bf087d 100644 --- a/app/config/collections2.php +++ b/app/config/collections2.php @@ -1051,7 +1051,7 @@ $collections = [ '$collection' => Database::COLLECTIONS, '$id' => 'certificates', 'name' => 'Certificates', - 'attributes' => [, + 'attributes' => [ [ '$id' => 'domain', 'type' => Database::VAR_STRING, diff --git a/app/controllers/general.php b/app/controllers/general.php index 8b599a44c..cfe06effb 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -281,7 +281,7 @@ App::init(function ($utopia, $request, $response, $console, $project, $dbForCons throw new Exception('Password reset is required', 412); } -}, ['utopia', 'request', 'response', 'console', 'project', 'consoleDB', 'user', 'locale', 'clients']); +}, ['utopia', 'request', 'response', 'console', 'project', 'dbForConsole', 'user', 'locale', 'clients']); App::options(function ($request, $response) { /** @var Utopia\Swoole\Request $request */ From c04e2ba924cf442d800b56091e3dee54d8ecd62b Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 16 Jul 2021 09:49:54 -0400 Subject: [PATCH 86/90] Remove $ from projectId attribute --- app/workers/tasks.php | 2 +- src/Appwrite/Utopia/Response/Model/Task.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/workers/tasks.php b/app/workers/tasks.php index 287aea5fa..cb24c65a9 100644 --- a/app/workers/tasks.php +++ b/app/workers/tasks.php @@ -33,7 +33,7 @@ class TasksV1 extends Worker $db = $register->get('db'); $cache = $register->get('cache'); - $projectId = $this->args['$projectId'] ?? null; + $projectId = $this->args['projectId'] ?? null; $taskId = $this->args['$id'] ?? null; $updated = $this->args['updated'] ?? null; $next = $this->args['next'] ?? null; diff --git a/src/Appwrite/Utopia/Response/Model/Task.php b/src/Appwrite/Utopia/Response/Model/Task.php index 974bd4baf..fa5816ed1 100644 --- a/src/Appwrite/Utopia/Response/Model/Task.php +++ b/src/Appwrite/Utopia/Response/Model/Task.php @@ -21,7 +21,7 @@ class Task extends Model 'default' => '', 'example' => '5e5ea5c16897e', ]) - ->addRule('$projectId', [ + ->addRule('projectId', [ 'type' => self::TYPE_STRING, 'description' => 'Project ID.', 'default' => '', From 78dd02e81d58a88df4d7658a2c470003505cb752 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 16 Jul 2021 09:50:25 -0400 Subject: [PATCH 87/90] Revert increased curl timeout --- tests/e2e/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/Client.php b/tests/e2e/Client.php index 3bf4b164d..82fd642a0 100644 --- a/tests/e2e/Client.php +++ b/tests/e2e/Client.php @@ -189,7 +189,7 @@ class Client curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36'); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0); - curl_setopt($ch, CURLOPT_TIMEOUT, 60); + curl_setopt($ch, CURLOPT_TIMEOUT, 15); curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders) { $len = strlen($header); $header = explode(':', $header, 2); From 2619157dd0cc09b52a3644a5f97f5a800a197536 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 16 Jul 2021 09:53:55 -0400 Subject: [PATCH 88/90] Add inline todos --- app/controllers/api/database.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 2dc9d149d..87d70167d 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -237,8 +237,11 @@ App::post('/v1/database/collections/:collectionId/attributes') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_ATTRIBUTE) ->param('collectionId', '', new UID(), 'Collection unique ID. You can create a new collection using the Database service [server integration](/docs/server/database#createCollection).') + // TODO@kodumbeats attributeId ->param('id', '', new Key(), 'Attribute ID.') + // TODO@kodumbeats whitelist (allowlist) ->param('type', null, new Text(8), 'Attribute type.') + // TODO@kodumbeats hide size for ints/floats/bools ->param('size', null, new Integer(), 'Attribute size for text attributes, in number of characters. For integers, floats, or bools, use 0.') ->param('required', null, new Boolean(), 'Is attribute required?') ->param('default', null, new Wildcard(), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true) @@ -745,6 +748,7 @@ App::get('/v1/database/collections/:collectionId/documents') ->param('queries', [], new ArrayList(new Text(128)), 'Array of query strings.', true) ->param('limit', 25, new Range(0, 100), 'Maximum number of documents to return in response. Use this value to manage pagination. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) ->param('offset', 0, new Range(0, 900000000), 'Offset value. The default value is 0. Use this param to manage pagination.', true) + // TODO@kodumbeats 'after' param for pagination ->param('orderAttributes', [], new ArrayList(new Text(128)), 'Array of attributes used to sort results.', true) ->param('orderTypes', [], new ArrayList(new WhiteList(['DESC', 'ASC'], true)), 'Array of order directions for sorting attribtues. Possible values are DESC for descending order, or ASC for ascending order.', true) ->inject('response') From ce742b104e0008ef166f50e72567f9de6ecc3d26 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 16 Jul 2021 10:32:20 -0400 Subject: [PATCH 89/90] Remove $ from task document --- app/controllers/api/projects.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 6b2591dca..28694a0b1 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -1006,7 +1006,7 @@ App::post('/v1/projects/:projectId/tasks') $task = new Document([ '$id' => $dbForConsole->getId(), - '$projectId' => $project->getId(), + 'projectId' => $project->getId(), 'name' => $name, 'status' => $status, 'schedule' => $schedule, From 3c475e37a99ea6359eeb6e3b97367eb662bb3f45 Mon Sep 17 00:00:00 2001 From: kodumbeats Date: Fri, 16 Jul 2021 10:35:46 -0400 Subject: [PATCH 90/90] Add delay to wait for db before initialization --- app/http.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/http.php b/app/http.php index becb7aa0b..ee8a073b0 100644 --- a/app/http.php +++ b/app/http.php @@ -69,6 +69,9 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) { return $app; }); + // wait for database to be ready + sleep(5); + $dbForConsole = $app->getResource('dbForConsole'); /** @var Utopia\Database\Database $dbForConsole */ if(!$dbForConsole->exists()) {