From 33699ea2c463c2dbce39543220331c00163c8dc1 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Tue, 5 Oct 2021 12:30:33 +0200 Subject: [PATCH] feat(controllers): implement before cursor pagination --- app/controllers/api/database.php | 46 +++--- app/controllers/api/functions.php | 45 ++--- app/controllers/api/projects.php | 15 +- app/controllers/api/storage.php | 15 +- app/controllers/api/teams.php | 31 ++-- app/controllers/api/users.php | 15 +- app/tasks/usage.php | 4 +- app/workers/database.php | 8 +- composer.json | 2 +- composer.lock | 154 +++++++++--------- tests/e2e/Services/Database/DatabaseBase.php | 119 +++++++++++++- .../Database/DatabaseCustomServerTest.php | 47 +++++- .../Functions/FunctionsCustomClientTest.php | 27 ++- .../Functions/FunctionsCustomServerTest.php | 26 ++- .../Projects/ProjectsConsoleClientTest.php | 25 ++- tests/e2e/Services/Storage/StorageBase.php | 24 ++- tests/e2e/Services/Teams/TeamsBase.php | 26 ++- tests/e2e/Services/Teams/TeamsBaseClient.php | 2 +- tests/e2e/Services/Users/UsersBase.php | 30 +++- 19 files changed, 481 insertions(+), 180 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index 26d93ae5be..a6ebba50db 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -93,7 +93,7 @@ $attributesCallback = function ($collectionId, $attribute, $response, $dbForInte throw new Exception('Attribute already exists', 409); } - $dbForInternal->purgeDocument('collections', $collectionId); + $dbForInternal->deleteCachedCollection($collectionId); $database ->setParam('type', DATABASE_TYPE_CREATE_ATTRIBUTE) @@ -186,12 +186,13 @@ App::get('/v1/database/collections') ->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('after', '', new UID(), 'ID of the collection used as the starting point for the query, excluding the collection itself. Should be used for efficient pagination when working with large sets of data.', true) + ->param('cursor', '', new UID(), 'ID of the collection used as the starting point for the query, excluding the collection itself. Should be used for efficient pagination when working with large sets of data.', true) + ->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true) ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true) ->inject('response') ->inject('dbForInternal') ->inject('usage') - ->action(function ($search, $limit, $offset, $after, $orderType, $response, $dbForInternal, $usage) { + ->action(function ($search, $limit, $offset, $cursor, $cursorDirection, $orderType, $response, $dbForInternal, $usage) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal */ @@ -201,18 +202,18 @@ App::get('/v1/database/collections') $queries[] = new Query('name', Query::TYPE_SEARCH, [$search]); } - if (!empty($after)) { - $afterCollection = $dbForInternal->getDocument('collections', $after); + if (!empty($cursor)) { + $cursorCollection = $dbForInternal->getDocument('collections', $cursor); - if ($afterCollection->isEmpty()) { - throw new Exception("Collection '{$after}' for the 'after' value not found.", 400); + if ($cursorCollection->isEmpty()) { + throw new Exception("Collection '{$cursor}' for the 'cursor' value not found.", 400); } } $usage->setParam('database.collections.read', 1); $response->dynamic(new Document([ - 'collections' => $dbForInternal->find('collections', $queries, $limit, $offset, [], [$orderType], $afterCollection ?? null), + 'collections' => $dbForInternal->find('collections', $queries, $limit, $offset, [], [$orderType], $cursorCollection ?? null, $cursorDirection), 'sum' => $dbForInternal->count('collections', $queries, APP_LIMIT_COUNT), ]), Response::MODEL_COLLECTION_LIST); }); @@ -1060,7 +1061,7 @@ App::delete('/v1/database/collections/:collectionId/attributes/:attributeId') } $attribute = $dbForInternal->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'deleting')); - $dbForInternal->purgeDocument('collections', $collectionId); + $dbForInternal->deleteCachedCollection($collectionId); $database ->setParam('type', DATABASE_TYPE_DELETE_ATTRIBUTE) @@ -1167,7 +1168,7 @@ App::post('/v1/database/collections/:collectionId/indexes') throw new Exception('Index already exists', 409); } - $dbForInternal->purgeDocument('collections', $collectionId); + $dbForInternal->deleteCachedCollection($collectionId); $database ->setParam('type', DATABASE_TYPE_CREATE_INDEX) @@ -1312,7 +1313,7 @@ App::delete('/v1/database/collections/:collectionId/indexes/:indexId') } $index = $dbForInternal->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'deleting')); - $dbForInternal->purgeDocument('collections', $collectionId); + $dbForInternal->deleteCachedCollection($collectionId); $database ->setParam('type', DATABASE_TYPE_DELETE_INDEX) @@ -1365,7 +1366,7 @@ App::post('/v1/database/collections/:collectionId/documents') /** @var Utopia\Database\Document $user */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Stats\Stats $usage */ - + $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array if (empty($data)) { @@ -1375,7 +1376,7 @@ App::post('/v1/database/collections/:collectionId/documents') if (isset($data['$id'])) { throw new Exception('$id is not allowed for creating new documents, try update instead', 400); } - + $collection = $dbForInternal->getDocument('collections', $collectionId); if ($collection->isEmpty()) { @@ -1427,14 +1428,15 @@ 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) - ->param('after', '', new UID(), 'ID of the document used as the starting point for the query, excluding the document itself. Should be used for efficient pagination when working with large sets of data.', true) + ->param('cursor', '', new UID(), 'ID of the document used as the starting point for the query, excluding the document itself. Should be used for efficient pagination when working with large sets of data.', true) + ->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', 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) ->inject('response') ->inject('dbForInternal') ->inject('dbForExternal') ->inject('usage') - ->action(function ($collectionId, $queries, $limit, $offset, $after, $orderAttributes, $orderTypes, $response, $dbForInternal, $dbForExternal, $usage) { + ->action(function ($collectionId, $queries, $limit, $offset, $cursor, $cursorDirection, $orderAttributes, $orderTypes, $response, $dbForInternal, $dbForExternal, $usage) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal */ /** @var Utopia\Database\Database $dbForExternal */ @@ -1457,22 +1459,22 @@ App::get('/v1/database/collections/:collectionId/documents') throw new Exception($validator->getDescription(), 400); } - if (!empty($after)) { - $afterDocument = $dbForExternal->getDocument($collectionId, $after); + if (!empty($cursor)) { + $cursorDocument = $dbForExternal->getDocument($collectionId, $cursor); - if ($afterDocument->isEmpty()) { - throw new Exception("Document '{$after}' for the 'after' value not found.", 400); + if ($cursorDocument->isEmpty()) { + throw new Exception("Document '{$cursor}' for the 'cursor' value not found.", 400); } } $usage ->setParam('database.documents.read', 1) ->setParam('collectionId', $collectionId) - ; - + ; + $response->dynamic(new Document([ 'sum' => $dbForExternal->count($collectionId, $queries, APP_LIMIT_COUNT), - 'documents' => $dbForExternal->find($collectionId, $queries, $limit, $offset, $orderAttributes, $orderTypes, $afterDocument ?? null), + 'documents' => $dbForExternal->find($collectionId, $queries, $limit, $offset, $orderAttributes, $orderTypes, $cursorDocument ?? null, $cursorDirection), ]), Response::MODEL_DOCUMENT_LIST); }); diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 2a67c6d4c6..356f3d8386 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -88,26 +88,27 @@ App::get('/v1/functions') ->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, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true) - ->param('after', '', new UID(), 'ID of the function used as the starting point for the query, excluding the function itself. Should be used for efficient pagination when working with large sets of data.', true) + ->param('cursor', '', new UID(), 'ID of the function used as the starting point for the query, excluding the function itself. Should be used for efficient pagination when working with large sets of data.', true) + ->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true) ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true) ->inject('response') ->inject('dbForInternal') - ->action(function ($search, $limit, $offset, $after, $orderType, $response, $dbForInternal) { + ->action(function ($search, $limit, $offset, $cursor, $cursorDirection, $orderType, $response, $dbForInternal) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal */ $queries = ($search) ? [new Query('name', Query::TYPE_SEARCH, [$search])] : []; - if (!empty($after)) { - $afterFunction = $dbForInternal->getDocument('functions', $after); + if (!empty($cursor)) { + $cursorFunction = $dbForInternal->getDocument('functions', $cursor); - if ($afterFunction->isEmpty()) { - throw new Exception("Function '{$after}' for the 'after' value not found.", 400); + if ($cursorFunction->isEmpty()) { + throw new Exception("Function '{$cursor}' for the 'cursor' value not found.", 400); } } $response->dynamic(new Document([ - 'functions' => $dbForInternal->find('functions', $queries, $limit, $offset, [], [$orderType], $afterFunction ?? null), + 'functions' => $dbForInternal->find('functions', $queries, $limit, $offset, [], [$orderType], $cursorFunction ?? null, $cursorDirection), 'sum' => $dbForInternal->count('functions', $queries, APP_LIMIT_COUNT), ]), Response::MODEL_FUNCTION_LIST); }); @@ -481,11 +482,12 @@ App::get('/v1/functions/:functionId/tags') ->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, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true) - ->param('after', '', new UID(), 'ID of the tag used as the starting point for the query, excluding the tag itself. Should be used for efficient pagination when working with large sets of data.', true) + ->param('cursor', '', new UID(), 'ID of the tag used as the starting point for the query, excluding the tag itself. Should be used for efficient pagination when working with large sets of data.', true) + ->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true) ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true) ->inject('response') ->inject('dbForInternal') - ->action(function ($functionId, $search, $limit, $offset, $after, $orderType, $response, $dbForInternal) { + ->action(function ($functionId, $search, $limit, $offset, $cursor, $cursorDirection, $orderType, $response, $dbForInternal) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal */ @@ -497,15 +499,15 @@ App::get('/v1/functions/:functionId/tags') $queries[] = new Query('functionId', Query::TYPE_EQUAL, [$function->getId()]); - if (!empty($after)) { - $afterTag = $dbForInternal->getDocument('tags', $after); + if (!empty($cursor)) { + $cursorTag = $dbForInternal->getDocument('tags', $cursor); - if ($afterTag->isEmpty()) { - throw new Exception("Tag '{$after}' for the 'after' value not found.", 400); + if ($cursorTag->isEmpty()) { + throw new Exception("Tag '{$cursor}' for the 'cursor' value not found.", 400); } } - $results = $dbForInternal->find('tags', $queries, $limit, $offset, [], [$orderType], $afterTag ?? null); + $results = $dbForInternal->find('tags', $queries, $limit, $offset, [], [$orderType], $cursorTag ?? null, $cursorDirection); $sum = $dbForInternal->count('tags', $queries, APP_LIMIT_COUNT); $response->dynamic(new Document([ @@ -732,10 +734,11 @@ App::get('/v1/functions/:functionId/executions') ->param('functionId', '', new UID(), 'Function unique ID.') ->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, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true) - ->param('after', '', new UID(), 'ID of the execution used as the starting point for the query, excluding the execution itself. Should be used for efficient pagination when working with large sets of data.', true) + ->param('cursor', '', new UID(), 'ID of the execution used as the starting point for the query, excluding the execution itself. Should be used for efficient pagination when working with large sets of data.', true) + ->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true) ->inject('response') ->inject('dbForInternal') - ->action(function ($functionId, $limit, $offset, $after, $response, $dbForInternal) { + ->action(function ($functionId, $limit, $offset, $cursor, $cursorDirection, $response, $dbForInternal) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal */ @@ -747,17 +750,17 @@ App::get('/v1/functions/:functionId/executions') throw new Exception('Function not found', 404); } - if (!empty($after)) { - $afterExecution = $dbForInternal->getDocument('executions', $after); + if (!empty($cursor)) { + $cursorExecution = $dbForInternal->getDocument('executions', $cursor); - if ($afterExecution->isEmpty()) { - throw new Exception("Execution '{$after}' for the 'after' value not found.", 400); + if ($cursorExecution->isEmpty()) { + throw new Exception("Execution '{$cursor}' for the 'cursor' value not found.", 400); } } $results = $dbForInternal->find('executions', [ new Query('functionId', Query::TYPE_EQUAL, [$function->getId()]), - ], $limit, $offset, [], [Database::ORDER_DESC], $afterExecution ?? null); + ], $limit, $offset, [], [Database::ORDER_DESC], $cursorExecution ?? null, $cursorDirection); $sum = $dbForInternal->count('executions', [ new Query('functionId', Query::TYPE_EQUAL, [$function->getId()]), diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 3c7dbb8e5e..58f7fea4cc 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -163,25 +163,26 @@ App::get('/v1/projects') ->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, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true) - ->param('after', '', new UID(), 'ID of the project used as the starting point for the query, excluding the project itself. Should be used for efficient pagination when working with large sets of data.', true) + ->param('cursor', '', new UID(), 'ID of the project used as the starting point for the query, excluding the project itself. Should be used for efficient pagination when working with large sets of data.', true) + ->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true) ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true) ->inject('response') ->inject('dbForConsole') - ->action(function ($search, $limit, $offset, $after, $orderType, $response, $dbForConsole) { + ->action(function ($search, $limit, $offset, $cursor, $cursorDirection, $orderType, $response, $dbForConsole) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForConsole */ $queries = ($search) ? [new Query('name', Query::TYPE_SEARCH, [$search])] : []; - if (!empty($after)) { - $afterProject = $dbForConsole->getDocument('projects', $after); + if (!empty($cursor)) { + $cursorProject = $dbForConsole->getDocument('projects', $cursor); - if ($afterProject->isEmpty()) { - throw new Exception("Project '{$after}' for the 'after' value not found.", 400); + if ($cursorProject->isEmpty()) { + throw new Exception("Project '{$cursor}' for the 'cursor' value not found.", 400); } } - $results = $dbForConsole->find('projects', $queries, $limit, $offset, [], [$orderType], $afterProject ?? null); + $results = $dbForConsole->find('projects', $queries, $limit, $offset, [], [$orderType], $cursorProject ?? null, $cursorDirection); $sum = $dbForConsole->count('projects', $queries, APP_LIMIT_COUNT); $response->dynamic(new Document([ diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 7ea048c5b3..adbee3cb71 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -175,23 +175,24 @@ App::get('/v1/storage/files') ->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, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true) - ->param('after', '', new UID(), 'ID of the file used as the starting point for the query, excluding the file itself. Should be used for efficient pagination when working with large sets of data.', true) + ->param('cursor', '', new UID(), 'ID of the file used as the starting point for the query, excluding the file itself. Should be used for efficient pagination when working with large sets of data.', true) + ->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true) ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true) ->inject('response') ->inject('dbForInternal') ->inject('usage') - ->action(function ($search, $limit, $offset, $after, $orderType, $response, $dbForInternal, $usage) { + ->action(function ($search, $limit, $offset, $cursor, $cursorDirection, $orderType, $response, $dbForInternal, $usage) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal */ /** @var Appwrite\Stats\Stats $usage */ $queries = ($search) ? [new Query('name', Query::TYPE_SEARCH, $search)] : []; - if (!empty($after)) { - $afterFile = $dbForInternal->getDocument('files', $after); + if (!empty($cursor)) { + $cursorFile = $dbForInternal->getDocument('files', $cursor); - if ($afterFile->isEmpty()) { - throw new Exception("File '{$after}' for the 'after' value not found.", 400); + if ($cursorFile->isEmpty()) { + throw new Exception("File '{$cursor}' for the 'cursor' value not found.", 400); } } @@ -201,7 +202,7 @@ App::get('/v1/storage/files') ; $response->dynamic(new Document([ - 'files' => $dbForInternal->find('files', $queries, $limit, $offset, [], [$orderType], $afterFile ?? null), + 'files' => $dbForInternal->find('files', $queries, $limit, $offset, [], [$orderType], $cursorFile ?? null, $cursorDirection), 'sum' => $dbForInternal->count('files', $queries, APP_LIMIT_COUNT), ]), Response::MODEL_FILE_LIST); }); diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 9926f2f3b1..2a5b982460 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -14,6 +14,7 @@ use Utopia\Validator\Text; use Utopia\Validator\Range; use Utopia\Validator\ArrayList; use Utopia\Validator\WhiteList; +use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Exception\Duplicate; use Utopia\Database\Query; @@ -99,25 +100,26 @@ App::get('/v1/teams') ->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, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true) - ->param('after', '', new UID(), 'ID of the team used as the starting point for the query, excluding the team itself. Should be used for efficient pagination when working with large sets of data.', true) + ->param('cursor', '', new UID(), 'ID of the team used as the starting point for the query, excluding the team itself. Should be used for efficient pagination when working with large sets of data.', true) + ->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true) ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true) ->inject('response') ->inject('dbForInternal') - ->action(function ($search, $limit, $offset, $after, $orderType, $response, $dbForInternal) { + ->action(function ($search, $limit, $offset, $cursor, $cursorDirection, $orderType, $response, $dbForInternal) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal */ $queries = ($search) ? [new Query('name', Query::TYPE_SEARCH, [$search])] : []; - if (!empty($after)) { - $afterTeam = $dbForInternal->getDocument('teams', $after); + if (!empty($cursor)) { + $cursorTeam = $dbForInternal->getDocument('teams', $cursor); - if ($afterTeam->isEmpty()) { - throw new Exception("Team '{$after}' for the 'after' value not found.", 400); + if ($cursorTeam->isEmpty()) { + throw new Exception("Team '{$cursor}' for the 'cursor' value not found.", 400); } } - $results = $dbForInternal->find('teams', $queries, $limit, $offset, [], [$orderType], $afterTeam ?? null); + $results = $dbForInternal->find('teams', $queries, $limit, $offset, [], [$orderType], $cursorTeam ?? null, $cursorDirection); $sum = $dbForInternal->count('teams', $queries, APP_LIMIT_COUNT); $response->dynamic(new Document([ @@ -424,11 +426,12 @@ App::get('/v1/teams/:teamId/memberships') ->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, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true) - ->param('after', '', new UID(), 'ID of the membership used as the starting point for the query, excluding the membership itself. Should be used for efficient pagination when working with large sets of data.', true) + ->param('cursor', '', new UID(), 'ID of the membership used as the starting point for the query, excluding the membership itself. Should be used for efficient pagination when working with large sets of data.', true) + ->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true) ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true) ->inject('response') ->inject('dbForInternal') - ->action(function ($teamId, $search, $limit, $offset, $after, $orderType, $response, $dbForInternal) { + ->action(function ($teamId, $search, $limit, $offset, $cursor, $cursorDirection, $orderType, $response, $dbForInternal) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal */ @@ -438,15 +441,15 @@ App::get('/v1/teams/:teamId/memberships') throw new Exception('Team not found', 404); } - if (!empty($after)) { - $afterMembership = $dbForInternal->getDocument('memberships', $after); + if (!empty($cursor)) { + $cursorMembership = $dbForInternal->getDocument('memberships', $cursor); - if ($afterMembership->isEmpty()) { - throw new Exception("Membership '{$after}' for the 'after' value not found.", 400); + if ($cursorMembership->isEmpty()) { + throw new Exception("Membership '{$cursor}' for the 'cursor' value not found.", 400); } } - $memberships = $dbForInternal->find('memberships', [new Query('teamId', Query::TYPE_EQUAL, [$teamId])], $limit, $offset, [], [$orderType], $afterMembership ?? null); + $memberships = $dbForInternal->find('memberships', [new Query('teamId', Query::TYPE_EQUAL, [$teamId])], $limit, $offset, [], [$orderType], $cursorMembership ?? null, $cursorDirection); $sum = $dbForInternal->count('memberships', [new Query('teamId', Query::TYPE_EQUAL, [$teamId])], APP_LIMIT_COUNT); $users = []; diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index c6c8207e21..64640a89dd 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -93,27 +93,28 @@ App::get('/v1/users') ->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, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true) - ->param('after', '', new UID(), 'ID of the user used as the starting point for the query, excluding the user itself. Should be used for efficient pagination when working with large sets of data.', true) + ->param('cursor', '', new UID(), 'ID of the user used as the starting point for the query, excluding the user itself. Should be used for efficient pagination when working with large sets of data.', true) + ->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true) ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true) ->inject('response') ->inject('dbForInternal') ->inject('usage') - ->action(function ($search, $limit, $offset, $after, $orderType, $response, $dbForInternal, $usage) { + ->action(function ($search, $limit, $offset, $cursor, $cursorDirection, $orderType, $response, $dbForInternal, $usage) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForInternal */ /** @var Appwrite\Stats\Stats $usage */ - if (!empty($after)) { - $afterUser = $dbForInternal->getDocument('users', $after); + if (!empty($cursor)) { + $cursorUser = $dbForInternal->getDocument('users', $cursor); - if ($afterUser->isEmpty()) { + if ($cursorUser->isEmpty()) { throw new Exception('User for after not found', 400); } } - $results = $dbForInternal->find('users', [], $limit, $offset, [], [$orderType], $afterUser ?? null); + $results = $dbForInternal->find('users', [], $limit, $offset, [], [$orderType], $cursorUser ?? null, $cursorDirection); $sum = $dbForInternal->count('users', [], APP_LIMIT_COUNT); - + $usage ->setParam('users.read', 1) ; diff --git a/app/tasks/usage.php b/app/tasks/usage.php index 4ff5e8cad9..701037fcca 100644 --- a/app/tasks/usage.php +++ b/app/tasks/usage.php @@ -342,7 +342,7 @@ $cli do { // list projects try { $attempts++; - $projects = $dbForConsole->find('projects', [], 100, orderAfter:$latestProject); + $projects = $dbForConsole->find('projects', [], 100, cursor:$latestProject); break; // leave the do-while if successful } catch (\Exception $e) { Console::warning("Console DB not ready yet. Retrying ({$attempts})..."); @@ -472,7 +472,7 @@ $cli do { // Loop over all the parent collection document for each sub collection $dbForProject->setNamespace("project_{$projectId}_{$options['namespace']}"); - $parents = $dbForProject->find($collection, [], 100, orderAfter:$latestParent); // Get all the parents for the sub collections for example for documents, this will get all the collections + $parents = $dbForProject->find($collection, [], 100, cursor:$latestParent); // Get all the parents for the sub collections for example for documents, this will get all the collections if (empty($parents)) { continue; diff --git a/app/workers/database.php b/app/workers/database.php index 2366b37998..f73c511060 100644 --- a/app/workers/database.php +++ b/app/workers/database.php @@ -94,7 +94,7 @@ class DatabaseV1 extends Worker $dbForInternal->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'failed')); } - $dbForInternal->purgeDocument('collections', $collectionId); + //$dbForInternal->deleteCachedCollection($collectionId); } /** @@ -120,7 +120,7 @@ class DatabaseV1 extends Worker $dbForInternal->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'failed')); } - $dbForInternal->purgeDocument('collections', $collectionId); + $dbForInternal->deleteCachedCollection($collectionId); } /** @@ -150,7 +150,7 @@ class DatabaseV1 extends Worker $dbForInternal->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'failed')); } - $dbForInternal->purgeDocument('collections', $collectionId); + $dbForInternal->deleteCachedCollection($collectionId); } /** @@ -177,6 +177,6 @@ class DatabaseV1 extends Worker $dbForInternal->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'failed')); } - $dbForInternal->purgeDocument('collections', $collectionId); + $dbForInternal->deleteCachedCollection($collectionId); } } diff --git a/composer.json b/composer.json index 3635007043..204585599f 100644 --- a/composer.json +++ b/composer.json @@ -45,7 +45,7 @@ "utopia-php/cache": "0.4.*", "utopia-php/cli": "0.11.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "dev-feat-adjusted-query-validator as 0.10.0", + "utopia-php/database": "0.10.*", "utopia-php/locale": "0.4.*", "utopia-php/registry": "0.5.*", "utopia-php/preloader": "0.2.*", diff --git a/composer.lock b/composer.lock index 8dac7bcc6d..b7dd4c5698 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "175f077512c575216c4c88f1d33c6d00", + "content-hash": "5ed5aceb1c5a8a31b2a1cd2122a74d78", "packages": [ { "name": "adhocore/jwt", @@ -248,16 +248,16 @@ }, { "name": "chillerlan/php-settings-container", - "version": "2.1.1", + "version": "2.1.2", "source": { "type": "git", "url": "https://github.com/chillerlan/php-settings-container.git", - "reference": "98ccc1b31b31a53bcb563465c4961879b2b93096" + "reference": "ec834493a88682dd69652a1eeaf462789ed0c5f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/98ccc1b31b31a53bcb563465c4961879b2b93096", - "reference": "98ccc1b31b31a53bcb563465c4961879b2b93096", + "url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/ec834493a88682dd69652a1eeaf462789ed0c5f5", + "reference": "ec834493a88682dd69652a1eeaf462789ed0c5f5", "shasum": "" }, "require": { @@ -307,7 +307,7 @@ "type": "ko_fi" } ], - "time": "2021-01-06T15:57:03+00:00" + "time": "2021-09-06T15:17:01+00:00" }, { "name": "colinmollenhour/credis", @@ -355,16 +355,16 @@ }, { "name": "composer/package-versions-deprecated", - "version": "1.11.99.3", + "version": "1.11.99.4", "source": { "type": "git", "url": "https://github.com/composer/package-versions-deprecated.git", - "reference": "fff576ac850c045158a250e7e27666e146e78d18" + "reference": "b174585d1fe49ceed21928a945138948cb394600" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/fff576ac850c045158a250e7e27666e146e78d18", - "reference": "fff576ac850c045158a250e7e27666e146e78d18", + "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b174585d1fe49ceed21928a945138948cb394600", + "reference": "b174585d1fe49ceed21928a945138948cb394600", "shasum": "" }, "require": { @@ -408,7 +408,7 @@ "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", "support": { "issues": "https://github.com/composer/package-versions-deprecated/issues", - "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.3" + "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.4" }, "funding": [ { @@ -424,7 +424,7 @@ "type": "tidelift" } ], - "time": "2021-08-17T13:49:14+00:00" + "time": "2021-09-13T08:41:34+00:00" }, { "name": "dragonmantank/cron-expression", @@ -1984,11 +1984,11 @@ }, { "name": "utopia-php/database", - "version": "dev-feat-adjusted-query-validator", + "version": "0.10.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database", - "reference": "cb73391371f70ddb54bc0000064b15c5f173cb7a" + "reference": "b7c60b0ec769a9050dd2b939b78ff1f5d4fa27e8" }, "require": { "ext-mongodb": "*", @@ -2037,7 +2037,7 @@ "upf", "utopia" ], - "time": "2021-08-23T14:18:47+00:00" + "time": "2021-10-04T17:23:25+00:00" }, { "name": "utopia-php/domains", @@ -2576,16 +2576,16 @@ "packages-dev": [ { "name": "amphp/amp", - "version": "v2.6.0", + "version": "v2.6.1", "source": { "type": "git", "url": "https://github.com/amphp/amp.git", - "reference": "caa95edeb1ca1bf7532e9118ede4a3c3126408cc" + "reference": "c5fc66a78ee38d7ac9195a37bacaf940eb3f65ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/amp/zipball/caa95edeb1ca1bf7532e9118ede4a3c3126408cc", - "reference": "caa95edeb1ca1bf7532e9118ede4a3c3126408cc", + "url": "https://api.github.com/repos/amphp/amp/zipball/c5fc66a78ee38d7ac9195a37bacaf940eb3f65ae", + "reference": "c5fc66a78ee38d7ac9195a37bacaf940eb3f65ae", "shasum": "" }, "require": { @@ -2653,7 +2653,7 @@ "support": { "irc": "irc://irc.freenode.org/amphp", "issues": "https://github.com/amphp/amp/issues", - "source": "https://github.com/amphp/amp/tree/v2.6.0" + "source": "https://github.com/amphp/amp/tree/v2.6.1" }, "funding": [ { @@ -2661,7 +2661,7 @@ "type": "github" } ], - "time": "2021-07-16T20:06:06+00:00" + "time": "2021-09-23T18:43:08+00:00" }, { "name": "amphp/byte-stream", @@ -3383,16 +3383,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.12.0", + "version": "v4.13.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "6608f01670c3cc5079e18c1dab1104e002579143" + "reference": "50953a2691a922aa1769461637869a0a2faa3f53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6608f01670c3cc5079e18c1dab1104e002579143", - "reference": "6608f01670c3cc5079e18c1dab1104e002579143", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/50953a2691a922aa1769461637869a0a2faa3f53", + "reference": "50953a2691a922aa1769461637869a0a2faa3f53", "shasum": "" }, "require": { @@ -3433,9 +3433,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.12.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.0" }, - "time": "2021-07-21T10:44:31+00:00" + "time": "2021-09-20T12:20:58+00:00" }, { "name": "openlss/lib-array2xml", @@ -3712,16 +3712,16 @@ }, { "name": "phpdocumentor/type-resolver", - "version": "1.4.0", + "version": "1.5.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0" + "reference": "a12f7e301eb7258bb68acd89d4aefa05c2906cae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", - "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/a12f7e301eb7258bb68acd89d4aefa05c2906cae", + "reference": "a12f7e301eb7258bb68acd89d4aefa05c2906cae", "shasum": "" }, "require": { @@ -3729,7 +3729,8 @@ "phpdocumentor/reflection-common": "^2.0" }, "require-dev": { - "ext-tokenizer": "*" + "ext-tokenizer": "*", + "psalm/phar": "^4.8" }, "type": "library", "extra": { @@ -3755,39 +3756,39 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.4.0" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.5.1" }, - "time": "2020-09-17T18:55:26+00:00" + "time": "2021-10-02T14:08:47+00:00" }, { "name": "phpspec/prophecy", - "version": "1.13.0", + "version": "1.14.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea" + "reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/be1996ed8adc35c3fd795488a653f4b518be70ea", - "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e", + "reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e", "shasum": "" }, "require": { "doctrine/instantiator": "^1.2", - "php": "^7.2 || ~8.0, <8.1", + "php": "^7.2 || ~8.0, <8.2", "phpdocumentor/reflection-docblock": "^5.2", "sebastian/comparator": "^3.0 || ^4.0", "sebastian/recursion-context": "^3.0 || ^4.0" }, "require-dev": { - "phpspec/phpspec": "^6.0", + "phpspec/phpspec": "^6.0 || ^7.0", "phpunit/phpunit": "^8.0 || ^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11.x-dev" + "dev-master": "1.x-dev" } }, "autoload": { @@ -3822,29 +3823,29 @@ ], "support": { "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/1.13.0" + "source": "https://github.com/phpspec/prophecy/tree/1.14.0" }, - "time": "2021-03-17T13:42:18+00:00" + "time": "2021-09-10T09:02:12+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.6", + "version": "9.2.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "f6293e1b30a2354e8428e004689671b83871edde" + "reference": "d4c798ed8d51506800b441f7a13ecb0f76f12218" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f6293e1b30a2354e8428e004689671b83871edde", - "reference": "f6293e1b30a2354e8428e004689671b83871edde", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/d4c798ed8d51506800b441f7a13ecb0f76f12218", + "reference": "d4c798ed8d51506800b441f7a13ecb0f76f12218", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.10.2", + "nikic/php-parser": "^4.12.0", "php": ">=7.3", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", @@ -3893,7 +3894,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.6" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.7" }, "funding": [ { @@ -3901,7 +3902,7 @@ "type": "github" } ], - "time": "2021-03-28T07:26:59+00:00" + "time": "2021-09-17T05:39:03+00:00" }, { "name": "phpunit/php-file-iterator", @@ -5313,16 +5314,16 @@ }, { "name": "symfony/console", - "version": "v5.3.6", + "version": "v5.3.7", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "51b71afd6d2dc8f5063199357b9880cea8d8bfe2" + "reference": "8b1008344647462ae6ec57559da166c2bfa5e16a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/51b71afd6d2dc8f5063199357b9880cea8d8bfe2", - "reference": "51b71afd6d2dc8f5063199357b9880cea8d8bfe2", + "url": "https://api.github.com/repos/symfony/console/zipball/8b1008344647462ae6ec57559da166c2bfa5e16a", + "reference": "8b1008344647462ae6ec57559da166c2bfa5e16a", "shasum": "" }, "require": { @@ -5392,7 +5393,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.3.6" + "source": "https://github.com/symfony/console/tree/v5.3.7" }, "funding": [ { @@ -5408,7 +5409,7 @@ "type": "tidelift" } ], - "time": "2021-07-27T19:10:22+00:00" + "time": "2021-08-25T20:02:16+00:00" }, { "name": "symfony/deprecation-contracts", @@ -5882,16 +5883,16 @@ }, { "name": "symfony/string", - "version": "v5.3.3", + "version": "v5.3.7", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1" + "reference": "8d224396e28d30f81969f083a58763b8b9ceb0a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1", - "reference": "bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1", + "url": "https://api.github.com/repos/symfony/string/zipball/8d224396e28d30f81969f083a58763b8b9ceb0a5", + "reference": "8d224396e28d30f81969f083a58763b8b9ceb0a5", "shasum": "" }, "require": { @@ -5945,7 +5946,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.3.3" + "source": "https://github.com/symfony/string/tree/v5.3.7" }, "funding": [ { @@ -5961,7 +5962,7 @@ "type": "tidelift" } ], - "time": "2021-06-27T11:44:38+00:00" + "time": "2021-08-26T08:00:08+00:00" }, { "name": "theseer/tokenizer", @@ -6015,16 +6016,16 @@ }, { "name": "twig/twig", - "version": "v2.14.6", + "version": "v2.14.7", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "27e5cf2b05e3744accf39d4c68a3235d9966d260" + "reference": "8e202327ee1ed863629de9b18a5ec70ac614d88f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/27e5cf2b05e3744accf39d4c68a3235d9966d260", - "reference": "27e5cf2b05e3744accf39d4c68a3235d9966d260", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/8e202327ee1ed863629de9b18a5ec70ac614d88f", + "reference": "8e202327ee1ed863629de9b18a5ec70ac614d88f", "shasum": "" }, "require": { @@ -6034,7 +6035,7 @@ }, "require-dev": { "psr/container": "^1.0", - "symfony/phpunit-bridge": "^4.4.9|^5.0.9" + "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" }, "type": "library", "extra": { @@ -6078,7 +6079,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v2.14.6" + "source": "https://github.com/twigphp/Twig/tree/v2.14.7" }, "funding": [ { @@ -6090,7 +6091,7 @@ "type": "tidelift" } ], - "time": "2021-05-16T12:12:47+00:00" + "time": "2021-09-17T08:39:54+00:00" }, { "name": "vimeo/psalm", @@ -6248,18 +6249,9 @@ "time": "2015-12-17T08:42:14+00:00" } ], - "aliases": [ - { - "package": "utopia-php/database", - "version": "dev-feat-adjusted-query-validator", - "alias": "0.10.0", - "alias_normalized": "0.10.0.0" - } - ], + "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "utopia-php/database": 20 - }, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -6281,5 +6273,5 @@ "platform-overrides": { "php": "8.0" }, - "plugin-api-version": "2.1.0" + "plugin-api-version": "2.0.0" } diff --git a/tests/e2e/Services/Database/DatabaseBase.php b/tests/e2e/Services/Database/DatabaseBase.php index 33ced61da4..8533d024d9 100644 --- a/tests/e2e/Services/Database/DatabaseBase.php +++ b/tests/e2e/Services/Database/DatabaseBase.php @@ -3,6 +3,7 @@ namespace Tests\E2E\Services\Database; use Tests\E2E\Client; +use Utopia\Database\Database; trait DatabaseBase { @@ -305,7 +306,7 @@ trait DatabaseBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'after' => $base['body']['documents'][0]['$id'] + 'cursor' => $base['body']['documents'][0]['$id'] ]); $this->assertEquals($documents['headers']['status-code'], 200); @@ -317,7 +318,7 @@ trait DatabaseBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'after' => $base['body']['documents'][2]['$id'] + 'cursor' => $base['body']['documents'][2]['$id'] ]); $this->assertEquals($documents['headers']['status-code'], 200); @@ -346,7 +347,7 @@ trait DatabaseBase ], $this->getHeaders()), [ 'orderAttributes' => ['releaseYear'], 'orderTypes' => ['ASC'], - 'after' => $base['body']['documents'][1]['$id'] + 'cursor' => $base['body']['documents'][1]['$id'] ]); $this->assertEquals($documents['headers']['status-code'], 200); @@ -376,7 +377,7 @@ trait DatabaseBase ], $this->getHeaders()), [ 'orderAttributes' => ['releaseYear'], 'orderTypes' => ['DESC'], - 'after' => $base['body']['documents'][1]['$id'] + 'cursor' => $base['body']['documents'][1]['$id'] ]); $this->assertEquals($documents['headers']['status-code'], 200); @@ -390,7 +391,7 @@ trait DatabaseBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'after' => 'unknown' + 'cursor' => 'unknown' ]); $this->assertEquals($documents['headers']['status-code'], 400); @@ -398,6 +399,114 @@ trait DatabaseBase return []; } + /** + * @depends testCreateDocument + */ + public function testListDocumentsBeforePagination(array $data):array + { + /** + * Test before without order. + */ + $base = $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())); + + $this->assertEquals($base['headers']['status-code'], 200); + $this->assertEquals('Captain America', $base['body']['documents'][0]['title']); + $this->assertEquals('Spider-Man: Far From Home', $base['body']['documents'][1]['title']); + $this->assertEquals('Spider-Man: Homecoming', $base['body']['documents'][2]['title']); + $this->assertCount(3, $base['body']['documents']); + + $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()), [ + 'cursor' => $base['body']['documents'][3]['$id'], + 'cursorDirection' => Database::CURSOR_BEFORE + ]); + + $this->assertEquals($documents['headers']['status-code'], 200); + $this->assertEquals($base['body']['documents'][1]['$id'], $documents['body']['documents'][0]['$id']); + $this->assertEquals($base['body']['documents'][2]['$id'], $documents['body']['documents'][1]['$id']); + $this->assertCount(2, $documents['body']['documents']); + + $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()), [ + 'cursor' => $base['body']['documents'][0]['$id'], + 'cursorDirection' => Database::CURSOR_BEFORE + ]); + + $this->assertEquals($documents['headers']['status-code'], 200); + $this->assertEmpty($documents['body']['documents']); + + /** + * Test with ASC order and after. + */ + $base = $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()), [ + 'orderAttributes' => ['releaseYear'], + 'orderTypes' => ['ASC'], + ]); + + $this->assertEquals($base['headers']['status-code'], 200); + $this->assertEquals(1944, $base['body']['documents'][0]['releaseYear']); + $this->assertEquals(2017, $base['body']['documents'][1]['releaseYear']); + $this->assertEquals(2019, $base['body']['documents'][2]['releaseYear']); + $this->assertCount(3, $base['body']['documents']); + + $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()), [ + 'orderAttributes' => ['releaseYear'], + 'orderTypes' => ['ASC'], + 'cursor' => $base['body']['documents'][1]['$id'], + 'cursorDirection' => Database::CURSOR_BEFORE + ]); + + $this->assertEquals($documents['headers']['status-code'], 200); + $this->assertEquals($base['body']['documents'][0]['$id'], $documents['body']['documents'][0]['$id']); + $this->assertCount(1, $documents['body']['documents']); + + /** + * Test with DESC order and after. + */ + $base = $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()), [ + 'orderAttributes' => ['releaseYear'], + 'orderTypes' => ['DESC'], + ]); + + $this->assertEquals($base['headers']['status-code'], 200); + $this->assertEquals(1944, $base['body']['documents'][2]['releaseYear']); + $this->assertEquals(2017, $base['body']['documents'][1]['releaseYear']); + $this->assertEquals(2019, $base['body']['documents'][0]['releaseYear']); + $this->assertCount(3, $base['body']['documents']); + + $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()), [ + 'orderAttributes' => ['releaseYear'], + 'orderTypes' => ['DESC'], + 'cursor' => $base['body']['documents'][1]['$id'], + 'cursorDirection' => Database::CURSOR_BEFORE + ]); + + $this->assertEquals($documents['headers']['status-code'], 200); + $this->assertEquals($base['body']['documents'][0]['$id'], $documents['body']['documents'][0]['$id']); + $this->assertCount(1, $documents['body']['documents']); + + return []; + } + /** * @depends testCreateDocument */ diff --git a/tests/e2e/Services/Database/DatabaseCustomServerTest.php b/tests/e2e/Services/Database/DatabaseCustomServerTest.php index 79750318b2..c00d3dae47 100644 --- a/tests/e2e/Services/Database/DatabaseCustomServerTest.php +++ b/tests/e2e/Services/Database/DatabaseCustomServerTest.php @@ -6,6 +6,7 @@ use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideServer; use Tests\E2E\Client; +use Utopia\Database\Database; class DatabaseCustomServerTest extends Scope { @@ -78,7 +79,7 @@ class DatabaseCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'after' => $base['body']['collections'][0]['$id'] + 'cursor' => $base['body']['collections'][0]['$id'] ]); $this->assertCount(1, $collections['body']['collections']); @@ -88,11 +89,53 @@ class DatabaseCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'after' => $base['body']['collections'][1]['$id'] + 'cursor' => $base['body']['collections'][1]['$id'] ]); $this->assertCount(0, $collections['body']['collections']); $this->assertEmpty($collections['body']['collections']); + + /** + * Test for Before + */ + $base = $this->client->call(Client::METHOD_GET, '/database/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $collections = $this->client->call(Client::METHOD_GET, '/database/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'cursor' => $base['body']['collections'][1]['$id'], + 'cursorDirection' => Database::CURSOR_BEFORE + ]); + + $this->assertCount(1, $collections['body']['collections']); + $this->assertEquals($base['body']['collections'][0]['$id'], $collections['body']['collections'][0]['$id']); + + $collections = $this->client->call(Client::METHOD_GET, '/database/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'cursor' => $base['body']['collections'][0]['$id'], + 'cursorDirection' => Database::CURSOR_BEFORE + ]); + + $this->assertCount(0, $collections['body']['collections']); + $this->assertEmpty($collections['body']['collections']); + + /** + * Test for FAILURE + */ + $response = $this->client->call(Client::METHOD_GET, '/database/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'cursor' => 'unknown', + ]); + + $this->assertEquals($response['headers']['status-code'], 400); } public function testDeleteAttribute(): array diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index 86f6fde482..076d3a706d 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -7,6 +7,7 @@ use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideClient; +use Utopia\Database\Database; class FunctionsCustomClientTest extends Scope { @@ -253,11 +254,35 @@ class FunctionsCustomClientTest extends Scope 'x-appwrite-project' => $projectId, 'x-appwrite-key' => $apikey, ], [ - 'after' => $base['body']['executions'][0]['$id'] + 'cursor' => $base['body']['executions'][0]['$id'] ]); $this->assertCount(1, $executions['body']['executions']); $this->assertEquals($base['body']['executions'][1]['$id'], $executions['body']['executions'][0]['$id']); + $executions = $this->client->call(Client::METHOD_GET, '/functions/'.$functionId.'/executions', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-key' => $apikey, + ], [ + 'cursor' => $base['body']['executions'][1]['$id'], + 'cursorDirection' => Database::CURSOR_BEFORE + ]); + + $this->assertCount(1, $executions['body']['executions']); + $this->assertEquals($base['body']['executions'][0]['$id'], $executions['body']['executions'][0]['$id']); + + /** + * Test for FAILURE + */ + $executions = $this->client->call(Client::METHOD_GET, '/functions/'.$functionId.'/executions', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-key' => $apikey, + ], [ + 'cursor' => 'unknown' + ]); + + $this->assertEquals(400, $executions['headers']['status-code']); } } diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index cb820b45c3..572af28938 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -7,6 +7,7 @@ use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideServer; +use Utopia\Database\Database; class FunctionsCustomServerTest extends Scope { @@ -115,13 +116,36 @@ class FunctionsCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'after' => $functions['body']['functions'][0]['$id'] + 'cursor' => $functions['body']['functions'][0]['$id'] ]); $this->assertEquals($response['headers']['status-code'], 200); $this->assertCount(1, $response['body']['functions']); $this->assertEquals($response['body']['functions'][0]['name'], 'Test 2'); + $response = $this->client->call(Client::METHOD_GET, '/functions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'cursor' => $functions['body']['functions'][1]['$id'], + 'cursorDirection' => Database::CURSOR_BEFORE + ]); + + $this->assertEquals($response['headers']['status-code'], 200); + $this->assertCount(1, $response['body']['functions']); + $this->assertEquals($response['body']['functions'][0]['name'], 'Test'); + + /** + * Test for FAILURE + */ + $response = $this->client->call(Client::METHOD_GET, '/functions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'cursor' => 'unknown', + ]); + + $this->assertEquals($response['headers']['status-code'], 400); return $data; } diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index 1842891492..347e689690 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -7,6 +7,7 @@ use Tests\E2E\Scopes\ProjectConsole; use Tests\E2E\Scopes\SideClient; use Tests\E2E\Services\Projects\ProjectsBase; use Tests\E2E\Client; +use Utopia\Database\Database; class ProjectsConsoleClientTest extends Scope { @@ -144,16 +145,38 @@ class ProjectsConsoleClientTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()),[ - 'after' => $response['body']['projects'][0]['$id'] + 'cursor' => $response['body']['projects'][0]['$id'] ]); $this->assertEquals(200, $response['headers']['status-code']); $this->assertNotEmpty($response['body']); $this->assertCount(1, $response['body']['projects']); $this->assertEquals('Project Test 2', $response['body']['projects'][0]['name']); + + $response = $this->client->call(Client::METHOD_GET, '/projects', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()),[ + 'cursor' => $response['body']['projects'][0]['$id'], + 'cursorDirection' => Database::CURSOR_BEFORE + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + $this->assertCount(1, $response['body']['projects']); + $this->assertEquals('Project Test', $response['body']['projects'][0]['name']); + /** * Test for FAILURE */ + $response = $this->client->call(Client::METHOD_GET, '/projects', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()),[ + 'cursor' => 'unknown' + ]); + + $this->assertEquals(400, $response['headers']['status-code']); return $data; } diff --git a/tests/e2e/Services/Storage/StorageBase.php b/tests/e2e/Services/Storage/StorageBase.php index 57a5ee6972..f1306baf1b 100644 --- a/tests/e2e/Services/Storage/StorageBase.php +++ b/tests/e2e/Services/Storage/StorageBase.php @@ -4,6 +4,7 @@ namespace Tests\E2E\Services\Storage; use CURLFile; use Tests\E2E\Client; +use Utopia\Database\Database; use Utopia\Image\Image; trait StorageBase @@ -190,16 +191,37 @@ trait StorageBase 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'limit' => 1, - 'after' => $files['body']['files'][0]['$id'] + 'cursor' => $files['body']['files'][0]['$id'] ]); $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals($files['body']['files'][1]['$id'], $response['body']['files'][0]['$id']); $this->assertCount(1, $response['body']['files']); + $response = $this->client->call(Client::METHOD_GET, '/storage/files', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'limit' => 1, + 'cursor' => $files['body']['files'][1]['$id'], + 'cursorDirection' => Database::CURSOR_BEFORE + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals($files['body']['files'][0]['$id'], $response['body']['files'][0]['$id']); + $this->assertCount(1, $response['body']['files']); + /** * Test for FAILURE */ + $response = $this->client->call(Client::METHOD_GET, '/storage/files', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'cursor' => 'unknown' + ]); + + $this->assertEquals(400, $response['headers']['status-code']); return $data; } diff --git a/tests/e2e/Services/Teams/TeamsBase.php b/tests/e2e/Services/Teams/TeamsBase.php index 6c1ebbe36a..fed06312fb 100644 --- a/tests/e2e/Services/Teams/TeamsBase.php +++ b/tests/e2e/Services/Teams/TeamsBase.php @@ -3,6 +3,7 @@ namespace Tests\E2E\Services\Teams; use Tests\E2E\Client; +use Utopia\Database\Database; trait TeamsBase { @@ -189,7 +190,7 @@ trait TeamsBase 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'limit' => 1, - 'after' => $teams['body']['teams'][0]['$id'] + 'cursor' => $teams['body']['teams'][0]['$id'] ]); $this->assertEquals(200, $response['headers']['status-code']); @@ -198,9 +199,32 @@ trait TeamsBase $this->assertCount(1, $response['body']['teams']); $this->assertEquals($teams['body']['teams'][1]['$id'], $response['body']['teams'][0]['$id']); + $response = $this->client->call(Client::METHOD_GET, '/teams', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'limit' => 1, + 'cursor' => $teams['body']['teams'][1]['$id'], + 'cursorDirection' => Database::CURSOR_BEFORE + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertGreaterThan(0, $response['body']['sum']); + $this->assertIsInt($response['body']['sum']); + $this->assertCount(1, $response['body']['teams']); + $this->assertEquals($teams['body']['teams'][0]['$id'], $response['body']['teams'][0]['$id']); + /** * Test for FAILURE */ + $response = $this->client->call(Client::METHOD_GET, '/teams', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'cursor' => 'unknown' + ]); + + $this->assertEquals(400, $response['headers']['status-code']); return []; } diff --git a/tests/e2e/Services/Teams/TeamsBaseClient.php b/tests/e2e/Services/Teams/TeamsBaseClient.php index 90964c4f6e..a226ce07e8 100644 --- a/tests/e2e/Services/Teams/TeamsBaseClient.php +++ b/tests/e2e/Services/Teams/TeamsBaseClient.php @@ -157,7 +157,7 @@ trait TeamsBaseClient 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'after' => $memberships['body']['memberships'][0]['$id'] + 'cursor' => $memberships['body']['memberships'][0]['$id'] ]); $this->assertEquals(200, $response['headers']['status-code']); diff --git a/tests/e2e/Services/Users/UsersBase.php b/tests/e2e/Services/Users/UsersBase.php index 90b59a8c16..d876061ac3 100644 --- a/tests/e2e/Services/Users/UsersBase.php +++ b/tests/e2e/Services/Users/UsersBase.php @@ -3,6 +3,7 @@ namespace Tests\E2E\Services\Users; use Tests\E2E\Client; +use Utopia\Database\Database; trait UsersBase { @@ -75,7 +76,7 @@ trait UsersBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'after' => $response['body']['users'][0]['$id'] + 'cursor' => $response['body']['users'][0]['$id'] ]); $this->assertEquals($response['headers']['status-code'], 200); @@ -84,6 +85,33 @@ trait UsersBase $this->assertCount(1, $response['body']['users']); $this->assertEquals($response['body']['users'][0]['$id'], 'user1'); + + $response = $this->client->call(Client::METHOD_GET, '/users', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'cursor' => 'user1', + 'cursorDirection' => Database::CURSOR_BEFORE + ]); + + $this->assertEquals($response['headers']['status-code'], 200); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['users']); + $this->assertCount(1, $response['body']['users']); + + $this->assertEquals($response['body']['users'][0]['$id'], $data['userId']); + + /** + * Test for FAILURE + */ + $response = $this->client->call(Client::METHOD_GET, '/users', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'cursor' => 'unknown' + ]); + + $this->assertEquals($response['headers']['status-code'], 400); } /**