diff --git a/app/config/collections.php b/app/config/collections.php index 6f284000ac..6ad859a136 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -52,6 +52,13 @@ $collections = [ 'lengths' => [], 'orders' => [], ], + [ + '$id' => ID::custom('_key_name'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['name'], + 'lengths' => [256], + 'orders' => [Database::ORDER_ASC], + ], ], ], 'collections' => [ @@ -150,6 +157,27 @@ $collections = [ 'lengths' => [], 'orders' => [], ], + [ + '$id' => ID::custom('_key_name'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['name'], + 'lengths' => [256], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_enabled'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['enabled'], + 'lengths' => [], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_documentSecurity'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['documentSecurity'], + 'lengths' => [], + 'orders' => [Database::ORDER_ASC], + ], ], ], @@ -1792,6 +1820,20 @@ $collections = [ 'lengths' => [], 'orders' => [], ], + [ + '$id' => ID::custom('_key_name'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['name'], + 'lengths' => [128], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_total'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['total'], + 'lengths' => [], + 'orders' => [Database::ORDER_ASC], + ], ], ], @@ -1940,6 +1982,41 @@ $collections = [ 'lengths' => [], 'orders' => [], ], + [ + '$id' => ID::custom('_key_userId'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['userId'], + 'lengths' => [Database::LENGTH_KEY], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_teamId'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['teamId'], + 'lengths' => [Database::LENGTH_KEY], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_invited'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['invited'], + 'lengths' => [], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_joined'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['joined'], + 'lengths' => [], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_confirm'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['confirm'], + 'lengths' => [], + 'orders' => [Database::ORDER_ASC], + ], ], ], @@ -2088,7 +2165,63 @@ $collections = [ 'attributes' => ['search'], 'lengths' => [2048], 'orders' => [Database::ORDER_ASC], - ] + ], + [ + '$id' => ID::custom('_key_name'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['name'], + 'lengths' => [2048], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_status'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['status'], + 'lengths' => [Database::LENGTH_KEY], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_runtime'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['runtime'], + 'lengths' => [2048], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_deployment'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['deployment'], + 'lengths' => [Database::LENGTH_KEY], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_schedule'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['schedule'], + 'lengths' => [128], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_scheduleNext'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['scheduleNext'], + 'lengths' => [], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_schedulePrevious'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['schedulePrevious'], + 'lengths' => [], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_timeout'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['timeout'], + 'lengths' => [], + 'orders' => [Database::ORDER_ASC], + ], ], ], @@ -2241,6 +2374,34 @@ $collections = [ 'lengths' => [], 'orders' => [], ], + [ + '$id' => ID::custom('_key_entrypoint'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['entrypoint'], + 'lengths' => [2048], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_size'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['size'], + 'lengths' => [], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_buildId'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['buildId'], + 'lengths' => [Database::LENGTH_KEY], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_activate'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['activate'], + 'lengths' => [], + 'orders' => [Database::ORDER_ASC], + ], ], ], @@ -2513,6 +2674,34 @@ $collections = [ 'lengths' => [], 'orders' => [], ], + [ + '$id' => ID::custom('_key_trigger'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['trigger'], + 'lengths' => [128], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_status'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['status'], + 'lengths' => [128], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_statusCode'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['statusCode'], + 'lengths' => [], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_time'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['time'], + 'lengths' => [], + 'orders' => [Database::ORDER_ASC], + ], ], ], @@ -2704,6 +2893,48 @@ $collections = [ 'lengths' => [2048], 'orders' => [Database::ORDER_ASC], ], + [ + '$id' => ID::custom('_key_enabled'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['enabled'], + 'lengths' => [], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_name'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['name'], + 'lengths' => [128], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_fileSecurity'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['fileSecurity'], + 'lengths' => [], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_maximumFileSize'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['maximumFileSize'], + 'lengths' => [], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_encryption'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['encryption'], + 'lengths' => [], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_antivirus'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['antivirus'], + 'lengths' => [], + 'orders' => [Database::ORDER_ASC], + ], ] ], @@ -3106,6 +3337,48 @@ $collections = [ 'lengths' => [Database::LENGTH_KEY], 'orders' => [Database::ORDER_ASC], ], + [ + '$id' => ID::custom('_key_name'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['name'], + 'lengths' => [2048], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_signature'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['signature'], + 'lengths' => [2048], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_mimeType'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['mimeType'], + 'lengths' => [127], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_sizeOriginal'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['sizeOriginal'], + 'lengths' => [], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_chunksTotal'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['chunksTotal'], + 'lengths' => [], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_chunksUploaded'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['chunksUploaded'], + 'lengths' => [], + 'orders' => [Database::ORDER_ASC], + ], ] ], ]; diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index aa5cc1b2d3..1309eade74 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -18,6 +18,9 @@ use Appwrite\OpenSSL\OpenSSL; use Appwrite\Template\Template; use Appwrite\URL\URL as URLParser; use Appwrite\Utopia\Database\Validator\CustomId; +use Appwrite\Utopia\Database\Validator\Queries; +use Appwrite\Utopia\Database\Validator\Query\Limit; +use Appwrite\Utopia\Database\Validator\Query\Offset; use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; use MaxMind\Db\Reader; @@ -1345,14 +1348,18 @@ App::get('/v1/account/logs') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_LOG_LIST) - ->param('limit', 25, new Range(0, 100), 'Maximum number of logs to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) - ->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true) + ->param('queries', [], new Queries(new Limit(), new Offset()), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Only supported methods are limit and offset', true) ->inject('response') ->inject('user') ->inject('locale') ->inject('geodb') ->inject('dbForProject') - ->action(function (int $limit, int $offset, Response $response, Document $user, Locale $locale, Reader $geodb, Database $dbForProject) { + ->action(function (array $queries, Response $response, Document $user, Locale $locale, Reader $geodb, Database $dbForProject) { + + $queries = Query::parseQueries($queries); + $grouped = Query::groupByType($queries); + $limit = $grouped['limit'] ?? APP_LIMIT_COUNT; + $offset = $grouped['offset'] ?? 0; $audit = new EventAudit($dbForProject); diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 1556d67aed..e530f2568d 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -36,16 +36,16 @@ use Appwrite\Network\Validator\Email; use Appwrite\Network\Validator\IP; use Appwrite\Network\Validator\URL; use Appwrite\Utopia\Database\Validator\CustomId; -use Appwrite\Utopia\Database\Validator\IndexedQueries; -use Appwrite\Utopia\Database\Validator\Query\Cursor as CursorQueryValidator; -use Appwrite\Utopia\Database\Validator\Query\Filter as FilterQueryValidator; -use Appwrite\Utopia\Database\Validator\Query\Limit as LimitQueryValidator; -use Appwrite\Utopia\Database\Validator\Query\Offset as OffsetQueryValidator; -use Appwrite\Utopia\Database\Validator\Query\Order as OrderQueryValidator; +use Appwrite\Utopia\Database\Validator\Query\Limit; +use Appwrite\Utopia\Database\Validator\Query\Offset; use Appwrite\Utopia\Response; use Appwrite\Detector\Detector; use Appwrite\Event\Database as EventDatabase; use Appwrite\Event\Event; +use Appwrite\Utopia\Database\Validator\Queries; +use Appwrite\Utopia\Database\Validator\Queries\Collections; +use Appwrite\Utopia\Database\Validator\Queries\Databases; +use Appwrite\Utopia\Database\Validator\Queries\Documents; use Utopia\Config\Config; use MaxMind\Db\Reader; @@ -233,38 +233,36 @@ App::get('/v1/databases') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_DATABASE_LIST) + ->param('queries', [], new Databases(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Databases::ALLOWED_ATTRIBUTES), 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), 'Maximum number of collection to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) - ->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this param to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', 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, can be either \'before\' or \'after\'.', true) - ->param('orderType', Database::ORDER_ASC, new WhiteList([Database::ORDER_ASC, Database::ORDER_DESC], true), 'Order result by ' . Database::ORDER_ASC . ' or ' . Database::ORDER_DESC . ' order.', true) ->inject('response') ->inject('dbForProject') - ->action(function (string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject) { + ->action(function (array $queries, string $search, Response $response, Database $dbForProject) { - $filterQueries = []; + $queries = Query::parseQueries($queries); if (!empty($search)) { - $filterQueries[] = Query::search('search', $search); + $queries[] = Query::search('search', $search); } - $queries = []; - $queries[] = Query::limit($limit); - $queries[] = Query::offset($offset); - $queries[] = $orderType === 'ASC' ? Query::orderAsc('') : Query::orderDesc(''); - if (!empty($cursor)) { - $cursorDocument = $dbForProject->getDocument('databases', $cursor); + // Get cursor document if there was a cursor query + $cursor = reset(Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE)); + if ($cursor) { + /** @var Query $cursor */ + $databaseId = $cursor->getValue(); + $cursorDocument = $dbForProject->getDocument('databases', $databaseId); if ($cursorDocument->isEmpty()) { - throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Collection '{$cursor}' for the 'cursor' value not found."); + throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Database '{$databaseId}' for the 'cursor' value not found."); } - $queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument); + $cursor->setValue($cursorDocument); } + $filterQueries = Query::groupByType($queries)['filters']; + $response->dynamic(new Document([ - 'databases' => $dbForProject->find('databases', \array_merge($filterQueries, $queries)), + 'databases' => $dbForProject->find('databases', $queries), 'total' => $dbForProject->count('databases', $filterQueries, APP_LIMIT_COUNT), ]), Response::MODEL_DATABASE_LIST); }); @@ -296,24 +294,23 @@ App::get('/v1/databases/:databaseId') }); App::get('/v1/databases/:databaseId/logs') - ->desc('List Collection Logs') + ->desc('List Database Logs') ->groups(['api', 'database']) - ->label('scope', 'collections.read') + ->label('scope', 'databases.read') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) ->label('sdk.namespace', 'databases') ->label('sdk.method', 'listLogs') - ->label('sdk.description', '/docs/references/databases/get-collection-logs.md') + ->label('sdk.description', '/docs/references/databases/get-logs.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_LOG_LIST) ->param('databaseId', '', new UID(), 'Database ID.') - ->param('limit', 25, new Range(0, 100), 'Maximum number of logs to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) - ->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true) + ->param('queries', [], new Queries(new Limit(), new Offset()), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Only supported methods are limit and offset', true) ->inject('response') ->inject('dbForProject') ->inject('locale') ->inject('geodb') - ->action(function (string $databaseId, int $limit, int $offset, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) { + ->action(function (string $databaseId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) { $database = $dbForProject->getDocument('databases', $databaseId); @@ -321,9 +318,17 @@ App::get('/v1/databases/:databaseId/logs') throw new Exception(Exception::DATABASE_NOT_FOUND); } + $queries = Query::parseQueries($queries); + $grouped = Query::groupByType($queries); + $limit = $grouped['limit'] ?? APP_LIMIT_COUNT; + $offset = $grouped['offset'] ?? 0; + $audit = new Audit($dbForProject); $resource = 'database/' . $databaseId; $logs = $audit->getLogsByResource($resource, $limit, $offset); + + $output = []; + foreach ($logs as $i => &$log) { $log['userAgent'] = (!empty($log['userAgent'])) ? $log['userAgent'] : 'UNKNOWN'; @@ -542,15 +547,11 @@ App::get('/v1/databases/:databaseId/collections') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_COLLECTION_LIST) ->param('databaseId', '', new UID(), 'Database ID.') + ->param('queries', [], new Collections(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Collections::ALLOWED_ATTRIBUTES), 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), 'Maximum number of collection to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) - ->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this param to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', 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, can be either \'before\' or \'after\'.', true) - ->param('orderType', Database::ORDER_ASC, new WhiteList([Database::ORDER_ASC, Database::ORDER_DESC], true), 'Order result by ' . Database::ORDER_ASC . ' or ' . Database::ORDER_DESC . ' order.', true) ->inject('response') ->inject('dbForProject') - ->action(function (string $databaseId, string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject) { + ->action(function (string $databaseId, array $queries, string $search, Response $response, Database $dbForProject) { $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); @@ -558,30 +559,30 @@ App::get('/v1/databases/:databaseId/collections') throw new Exception(Exception::DATABASE_NOT_FOUND); } - $filterQueries = []; + $queries = Query::parseQueries($queries); if (!empty($search)) { - $filterQueries[] = Query::search('search', $search); + $queries[] = Query::search('search', $search); } - $queries = []; - $queries[] = Query::limit($limit); - $queries[] = Query::offset($offset); - $queries[] = $orderType === 'ASC' ? Query::orderAsc('') : Query::orderDesc(''); - if (!empty($cursor)) { - $cursorDocument = $dbForProject->getDocument('database_' . $database->getInternalId(), $cursor); + // Get cursor document if there was a cursor query + $cursor = reset(Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE)); + if ($cursor) { + /** @var Query $cursor */ + $collectionId = $cursor->getValue(); + $cursorDocument = $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId); if ($cursorDocument->isEmpty()) { - throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Collection '{$cursor}' for the 'cursor' value not found."); + throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Collection '{$collectionId}' for the 'cursor' value not found."); } - $queries[] = $cursorDirection === Database::CURSOR_AFTER - ? Query::cursorAfter($cursorDocument) - : Query::cursorBefore($cursorDocument); + $cursor->setValue($cursorDocument); } + $filterQueries = Query::groupByType($queries)['filters']; + $response->dynamic(new Document([ - 'collections' => $dbForProject->find('database_' . $database->getInternalId(), \array_merge($filterQueries, $queries)), + 'collections' => $dbForProject->find('database_' . $database->getInternalId(), $queries), 'total' => $dbForProject->count('database_' . $database->getInternalId(), $filterQueries, APP_LIMIT_COUNT), ]), Response::MODEL_COLLECTION_LIST); }); @@ -637,13 +638,12 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/logs') ->label('sdk.response.model', Response::MODEL_LOG_LIST) ->param('databaseId', '', new UID(), 'Database ID.') ->param('collectionId', '', new UID(), 'Collection ID.') - ->param('limit', 25, new Range(0, 100), 'Maximum number of logs to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) - ->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true) + ->param('queries', [], new Queries(new Limit(), new Offset()), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Only supported methods are limit and offset', true) ->inject('response') ->inject('dbForProject') ->inject('locale') ->inject('geodb') - ->action(function (string $databaseId, string $collectionId, int $limit, int $offset, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) { + ->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) { $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); @@ -657,6 +657,11 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/logs') throw new Exception(Exception::COLLECTION_NOT_FOUND); } + $queries = Query::parseQueries($queries); + $grouped = Query::groupByType($queries); + $limit = $grouped['limit'] ?? APP_LIMIT_COUNT; + $offset = $grouped['offset'] ?? 0; + $audit = new Audit($dbForProject); $resource = 'database/' . $databaseId . '/collection/' . $collectionId; $logs = $audit->getLogsByResource($resource, $limit, $offset); @@ -1937,17 +1942,11 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') ->label('sdk.response.model', Response::MODEL_DOCUMENT_LIST) ->param('databaseId', '', new UID(), 'Database ID.') ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') - ->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/database#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true) - ->param('limit', 25, new Range(0, 100), 'Maximum number of documents to return in response. By default will return maximum 25 results. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' results allowed per request.', true) - ->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', 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. [learn more about pagination](https://appwrite.io/docs/pagination)', true) - ->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true) - ->param('orderAttributes', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of attributes used to sort results. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' order attributes are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true) - ->param('orderTypes', [], new ArrayList(new WhiteList([Database::ORDER_DESC, Database::ORDER_ASC], true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of order directions for sorting attributes. Possible values are DESC for descending order or ASC for ascending order. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' order types are allowed.', true) + ->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true) ->inject('response') ->inject('dbForProject') ->inject('mode') - ->action(function (string $databaseId, string $collectionId, array $queries, int $limit, int $offset, string $cursor, string $cursorDirection, array $orderAttributes, array $orderTypes, Response $response, Database $dbForProject, string $mode) { + ->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject, string $mode) { $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); @@ -1970,71 +1969,41 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') throw new Exception(Exception::USER_UNAUTHORIZED); } - if (!empty($queries)) { - $attributes = array_merge( - $collection->getAttribute('attributes', []), - [ - new Document([ - 'key' => '$id', - 'type' => Database::VAR_STRING, - 'array' => false, - ]), - new Document([ - 'key' => '$createdAt', - 'type' => Database::VAR_DATETIME, - 'array' => false, - ]), - new Document([ - 'key' => '$updatedAt', - 'type' => Database::VAR_DATETIME, - 'array' => false, - ]), - ] - ); - $validator = new IndexedQueries( - $attributes, - $collection->getAttribute('indexes', []), - new CursorQueryValidator(), - new FilterQueryValidator($attributes), - new LimitQueryValidator(), - new OffsetQueryValidator(), - new OrderQueryValidator(), - ); - if (!$validator->isValid($queries)) { - throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription()); - } + // Validate queries + $queriesValidator = new Documents($collection->getAttribute('attributes'), $collection->getAttribute('indexes')); + $validQueries = $queriesValidator->isValid($queries); + if (!$validQueries) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $queriesValidator->getDescription()); } - $filterQueries = Query::parseQueries($queries); + $queries = Query::parseQueries($queries); - $otherQueries = []; - $otherQueries[] = Query::limit($limit); - $otherQueries[] = Query::offset($offset); - foreach ($orderTypes as $i => $orderType) { - $otherQueries[] = $orderType === Database::ORDER_DESC ? Query::orderDesc($orderAttributes[$i] ?? '') : Query::orderAsc($orderAttributes[$i] ?? ''); - } + // Get cursor document if there was a cursor query + $cursor = reset(Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE)); + if ($cursor) { + /** @var Query $cursor */ + $documentId = $cursor->getValue(); - if (!empty($cursor)) { if ($documentSecurity && !$valid) { - $cursorDocument = $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $cursor); + $cursorDocument = $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId); } else { - $cursorDocument = Authorization::skip(fn() => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $cursor)); + $cursorDocument = Authorization::skip(fn() => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId)); } if ($cursorDocument->isEmpty()) { - throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Document '{$cursor}' for the 'cursor' value not found."); + throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Document '{$documentId}' for the 'cursor' value not found."); } - $otherQueries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument); + $cursor->setValue($cursorDocument); } - $allQueries = \array_merge($filterQueries, $otherQueries); + $filterQueries = Query::groupByType($queries)['filters']; if ($documentSecurity && !$valid) { - $documents = $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $allQueries); + $documents = $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries); $total = $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $filterQueries, APP_LIMIT_COUNT); } else { - $documents = Authorization::skip(fn () => $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $allQueries)); + $documents = Authorization::skip(fn () => $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries)); $total = Authorization::skip(fn () => $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $filterQueries, APP_LIMIT_COUNT)); } @@ -2127,13 +2096,12 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen ->param('databaseId', '', new UID(), 'Database ID.') ->param('collectionId', '', new UID(), 'Collection ID.') ->param('documentId', null, new UID(), 'Document ID.') - ->param('limit', 25, new Range(0, 100), 'Maximum number of logs to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) - ->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true) + ->param('queries', [], new Queries(new Limit(), new Offset()), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Only supported methods are limit and offset', true) ->inject('response') ->inject('dbForProject') ->inject('locale') ->inject('geodb') - ->action(function (string $databaseId, string $collectionId, string $documentId, int $limit, int $offset, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) { + ->action(function (string $databaseId, string $collectionId, string $documentId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) { $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); @@ -2153,6 +2121,11 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen throw new Exception(Exception::DOCUMENT_NOT_FOUND); } + $queries = Query::parseQueries($queries); + $grouped = Query::groupByType($queries); + $limit = $grouped['limit'] ?? APP_LIMIT_COUNT; + $offset = $grouped['offset'] ?? 0; + $audit = new Audit($dbForProject); $resource = 'database/' . $databaseId . '/collection/' . $collectionId . '/document/' . $document->getId(); $logs = $audit->getLogsByResource($resource, $limit, $offset); diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 6ea17eb195..29a30ec54b 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -22,6 +22,9 @@ use Utopia\Storage\Validator\Upload; use Appwrite\Utopia\Response; use Utopia\Swoole\Request; use Appwrite\Task\Validator\Cron; +use Appwrite\Utopia\Database\Validator\Queries\Deployments; +use Appwrite\Utopia\Database\Validator\Queries\Executions; +use Appwrite\Utopia\Database\Validator\Queries\Functions; use Utopia\App; use Utopia\Database\Database; use Utopia\Database\Document; @@ -102,38 +105,36 @@ App::get('/v1/functions') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_FUNCTION_LIST) + ->param('queries', [], new Functions(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Functions::ALLOWED_ATTRIBUTES), 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), 'Maximum number of functions to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) - ->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', 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. [learn more about pagination](https://appwrite.io/docs/pagination)', true) - ->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true) - ->param('orderType', Database::ORDER_ASC, new WhiteList([Database::ORDER_ASC, Database::ORDER_DESC], true), 'Order result by ' . Database::ORDER_ASC . ' or ' . Database::ORDER_DESC . ' order.', true) ->inject('response') ->inject('dbForProject') - ->action(function (string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject) { + ->action(function (array $queries, string $search, Response $response, Database $dbForProject) { - $filterQueries = []; + $queries = Query::parseQueries($queries); if (!empty($search)) { - $filterQueries[] = Query::search('search', $search); + $queries[] = Query::search('search', $search); } - $queries = []; - $queries[] = Query::limit($limit); - $queries[] = Query::offset($offset); - $queries[] = $orderType === Database::ORDER_ASC ? Query::orderAsc('') : Query::orderDesc(''); - if (!empty($cursor)) { - $cursorDocument = $dbForProject->getDocument('functions', $cursor); + // Get cursor document if there was a cursor query + $cursor = reset(Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE)); + if ($cursor) { + /** @var Query $cursor */ + $functionId = $cursor->getValue(); + $cursorDocument = $dbForProject->getDocument('functions', $functionId); if ($cursorDocument->isEmpty()) { - throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Function '{$cursor}' for the 'cursor' value not found."); + throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Function '{$functionId}' for the 'cursor' value not found."); } - $queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument); + $cursor->setValue($cursorDocument); } + $filterQueries = Query::groupByType($queries)['filters']; + $response->dynamic(new Document([ - 'functions' => $dbForProject->find('functions', \array_merge($filterQueries, $queries)), + 'functions' => $dbForProject->find('functions', $queries), 'total' => $dbForProject->count('functions', $filterQueries, APP_LIMIT_COUNT), ]), Response::MODEL_FUNCTION_LIST); }); @@ -768,15 +769,11 @@ App::get('/v1/functions/:functionId/deployments') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_DEPLOYMENT_LIST) ->param('functionId', '', new UID(), 'Function ID.') + ->param('queries', [], new Deployments(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Deployments::ALLOWED_ATTRIBUTES), 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), 'Maximum number of deployments to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) - ->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true) - ->param('cursor', '', new UID(), 'ID of the deployment used as the starting point for the query, excluding the deployment itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true) - ->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true) - ->param('orderType', Database::ORDER_ASC, new WhiteList([Database::ORDER_ASC, Database::ORDER_DESC], true), 'Order result by ' . Database::ORDER_ASC . ' or ' . Database::ORDER_DESC . ' order.', true) ->inject('response') ->inject('dbForProject') - ->action(function (string $functionId, string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject) { + ->action(function (string $functionId, array $queries, string $search, Response $response, Database $dbForProject) { $function = $dbForProject->getDocument('functions', $functionId); @@ -784,30 +781,33 @@ App::get('/v1/functions/:functionId/deployments') throw new Exception(Exception::FUNCTION_NOT_FOUND); } - $filterQueries = []; + $queries = Query::parseQueries($queries); if (!empty($search)) { - $filterQueries[] = Query::search('search', $search); + $queries[] = Query::search('search', $search); } - $filterQueries[] = Query::equal('resourceId', [$function->getId()]); - $filterQueries[] = Query::equal('resourceType', ['functions']); + // Set resource queries + $queries[] = Query::equal('resourceId', [$function->getId()]); + $queries[] = Query::equal('resourceType', ['functions']); - $queries = []; - $queries[] = Query::limit($limit); - $queries[] = Query::offset($offset); - $queries[] = $orderType === Database::ORDER_ASC ? Query::orderAsc('') : Query::orderDesc(''); - if (!empty($cursor)) { - $cursorDocument = $dbForProject->getDocument('deployments', $cursor); + // Get cursor document if there was a cursor query + $cursor = reset(Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE)); + if ($cursor) { + /** @var Query $cursor */ + $deploymentId = $cursor->getValue(); + $cursorDocument = $dbForProject->getDocument('deployments', $deploymentId); if ($cursorDocument->isEmpty()) { - throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Tag '{$cursor}' for the 'cursor' value not found."); + throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Deployment '{$deploymentId}' for the 'cursor' value not found."); } - $queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument); + $cursor->setValue($cursorDocument); } - $results = $dbForProject->find('deployments', \array_merge($filterQueries, $queries)); + $filterQueries = Query::groupByType($queries)['filters']; + + $results = $dbForProject->find('deployments', $queries); $total = $dbForProject->count('deployments', $filterQueries, APP_LIMIT_COUNT); foreach ($results as $result) { @@ -1123,14 +1123,11 @@ App::get('/v1/functions/:functionId/executions') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_EXECUTION_LIST) ->param('functionId', '', new UID(), 'Function ID.') - ->param('limit', 25, new Range(0, 100), 'Maximum number of executions to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) - ->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true) + ->param('queries', [], new Executions(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Executions::ALLOWED_ATTRIBUTES), true) ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', 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. [learn more about pagination](https://appwrite.io/docs/pagination)', true) - ->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true) ->inject('response') ->inject('dbForProject') - ->action(function (string $functionId, int $limit, int $offset, string $search, string $cursor, string $cursorDirection, Response $response, Database $dbForProject) { + ->action(function (string $functionId, array $queries, string $search, Response $response, Database $dbForProject) { $function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId)); @@ -1138,29 +1135,32 @@ App::get('/v1/functions/:functionId/executions') throw new Exception(Exception::FUNCTION_NOT_FOUND); } - $filterQueries = [ - Query::equal('functionId', [$function->getId()]) - ]; + $queries = Query::parseQueries($queries); if (!empty($search)) { - $filterQueries[] = Query::search('search', $search); + $queries[] = Query::search('search', $search); } - $queries = []; - $queries[] = Query::limit($limit); - $queries[] = Query::offset($offset); - $queries[] = Query::orderDesc(''); - if (!empty($cursor)) { - $cursorDocument = $dbForProject->getDocument('executions', $cursor); + // Set internal queries + $queries[] = Query::equal('functionId', [$function->getId()]); + + // Get cursor document if there was a cursor query + $cursor = reset(Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE)); + if ($cursor) { + /** @var Query $cursor */ + $executionId = $cursor->getValue(); + $cursorDocument = $dbForProject->getDocument('executions', $executionId); if ($cursorDocument->isEmpty()) { - throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Tag '{$cursor}' for the 'cursor' value not found."); + throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Execution '{$executionId}' for the 'cursor' value not found."); } - $queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument); + $cursor->setValue($cursorDocument); } - $results = $dbForProject->find('executions', \array_merge($filterQueries, $queries)); + $filterQueries = Query::groupByType($queries)['filters']; + + $results = $dbForProject->find('executions', $queries); $total = $dbForProject->count('executions', $filterQueries, APP_LIMIT_COUNT); $roles = Authorization::getRoles(); diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index f8b0630a7d..79b9b4752d 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -25,6 +25,8 @@ use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\Permissions; use Utopia\Database\Validator\UID; use Appwrite\Extend\Exception; +use Appwrite\Utopia\Database\Validator\Queries\Buckets; +use Appwrite\Utopia\Database\Validator\Queries\Files; use Utopia\Image\Image; use Utopia\Storage\Compression\Algorithms\GZIP; use Utopia\Storage\Device; @@ -150,38 +152,36 @@ App::get('/v1/storage/buckets') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_BUCKET_LIST) + ->param('queries', [], new Buckets(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Buckets::ALLOWED_ATTRIBUTES), 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, APP_LIMIT_COUNT), 'Results offset. The default value is 0. Use this param to manage pagination.', true) - ->param('cursor', '', new UID(), 'ID of the bucket used as the starting point for the query, excluding the bucket 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, can be either \'before\' or \'after\'.', true) - ->param('orderType', Database::ORDER_ASC, new WhiteList([Database::ORDER_ASC, Database::ORDER_DESC], true), 'Order result by ' . Database::ORDER_ASC . ' or ' . Database::ORDER_DESC . ' order.', true) ->inject('response') ->inject('dbForProject') - ->action(function (string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject) { + ->action(function (array $queries, string $search, Response $response, Database $dbForProject) { - $filterQueries = []; + $queries = Query::parseQueries($queries); if (!empty($search)) { - $filterQueries[] = Query::search('name', $search); + $queries[] = Query::search('search', $search); } - $queries = []; - $queries[] = Query::limit($limit); - $queries[] = Query::offset($offset); - $queries[] = $orderType === Database::ORDER_ASC ? Query::orderAsc('') : Query::orderDesc(''); - if (!empty($cursor)) { - $cursorDocument = $dbForProject->getDocument('buckets', $cursor); + // Get cursor document if there was a cursor query + $cursor = reset(Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE)); + if ($cursor) { + /** @var Query $cursor */ + $bucketId = $cursor->getValue(); + $cursorDocument = $dbForProject->getDocument('buckets', $bucketId); if ($cursorDocument->isEmpty()) { - throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Bucket '{$cursor}' for the 'cursor' value not found."); + throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Bucket '{$bucketId}' for the 'cursor' value not found."); } - $queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument); + $cursor->setValue($cursorDocument); } + $filterQueries = Query::groupByType($queries)['filters']; + $response->dynamic(new Document([ - 'buckets' => $dbForProject->find('buckets', \array_merge($filterQueries, $queries)), + 'buckets' => $dbForProject->find('buckets', $queries), 'total' => $dbForProject->count('buckets', $filterQueries, APP_LIMIT_COUNT), ]), Response::MODEL_BUCKET_LIST); }); @@ -651,16 +651,12 @@ App::get('/v1/storage/buckets/:bucketId/files') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_FILE_LIST) ->param('bucketId', null, new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).') + ->param('queries', [], new Files(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Files::ALLOWED_ATTRIBUTES), 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), 'Maximum number of files to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) - ->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this param to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', 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. [learn more about pagination](https://appwrite.io/docs/pagination)', true) - ->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true) - ->param('orderType', Database::ORDER_ASC, new WhiteList([Database::ORDER_ASC, Database::ORDER_DESC], true), 'Order result by ' . Database::ORDER_ASC . ' or ' . Database::ORDER_DESC . ' order.', true) ->inject('response') ->inject('dbForProject') ->inject('mode') - ->action(function (string $bucketId, string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject, string $mode) { + ->action(function (string $bucketId, array $queries, string $search, Response $response, Database $dbForProject, string $mode) { $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); @@ -675,35 +671,38 @@ App::get('/v1/storage/buckets/:bucketId/files') throw new Exception(Exception::USER_UNAUTHORIZED); } - $filterQueries = []; + $queries = Query::parseQueries($queries); if (!empty($search)) { - $filterQueries[] = Query::search('name', $search); + $queries[] = Query::search('search', $search); } - $queries = []; - $queries[] = Query::limit($limit); - $queries[] = Query::offset($offset); - $queries[] = $orderType === Database::ORDER_ASC ? Query::orderAsc('') : Query::orderDesc(''); - if (!empty($cursor)) { + // Get cursor document if there was a cursor query + $cursor = reset(Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE)); + if ($cursor) { + /** @var Query $cursor */ + $fileId = $cursor->getValue(); + if ($fileSecurity && !$valid) { - $cursorDocument = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $cursor); + $cursorDocument = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId); } else { - $cursorDocument = Authorization::skip(fn() => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $cursor)); + $cursorDocument = Authorization::skip(fn() => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId)); } if ($cursorDocument->isEmpty()) { - throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "File '{$cursor}' for the 'cursor' value not found."); + throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "File '{$fileId}' for the 'cursor' value not found."); } - $queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument); + $cursor->setValue($cursorDocument); } + $filterQueries = Query::groupByType($queries)['filters']; + if ($fileSecurity && !$valid) { - $files = $dbForProject->find('bucket_' . $bucket->getInternalId(), \array_merge($filterQueries, $queries)); + $files = $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries); $total = $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT); } else { - $files = Authorization::skip(fn () => $dbForProject->find('bucket_' . $bucket->getInternalId(), \array_merge($filterQueries, $queries))); + $files = Authorization::skip(fn () => $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries)); $total = Authorization::skip(fn () => $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT)); } diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index f5b1de2a7a..485e4e0dc3 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -10,6 +10,11 @@ use Appwrite\Network\Validator\Email; use Appwrite\Network\Validator\Host; use Appwrite\Template\Template; use Appwrite\Utopia\Database\Validator\CustomId; +use Appwrite\Utopia\Database\Validator\Queries; +use Appwrite\Utopia\Database\Validator\Queries\Memberships; +use Appwrite\Utopia\Database\Validator\Queries\Teams; +use Appwrite\Utopia\Database\Validator\Query\Limit; +use Appwrite\Utopia\Database\Validator\Query\Offset; use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; use MaxMind\Db\Reader; @@ -126,37 +131,35 @@ App::get('/v1/teams') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_TEAM_LIST) + ->param('queries', [], new Teams(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Teams::ALLOWED_ATTRIBUTES), 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), 'Maximum number of teams to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) - ->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this param to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', 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. [learn more about pagination](https://appwrite.io/docs/pagination)', true) - ->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true) - ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true) ->inject('response') ->inject('dbForProject') - ->action(function (string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject) { + ->action(function (array $queries, string $search, Response $response, Database $dbForProject) { - $filterQueries = []; + $queries = Query::parseQueries($queries); if (!empty($search)) { - $filterQueries[] = Query::search('search', $search); + $queries[] = Query::search('search', $search); } - $queries = []; - $queries[] = Query::limit($limit); - $queries[] = Query::offset($offset); - $queries[] = $orderType === Database::ORDER_ASC ? Query::orderAsc('') : Query::orderDesc(''); - if (!empty($cursor)) { - $cursorDocument = $dbForProject->getDocument('teams', $cursor); + // Get cursor document if there was a cursor query + $cursor = reset(Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE)); + if ($cursor) { + /** @var Query $cursor */ + $teamId = $cursor->getValue(); + $cursorDocument = $dbForProject->getDocument('teams', $teamId); if ($cursorDocument->isEmpty()) { - throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Team '{$cursor}' for the 'cursor' value not found."); + throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Team '{$teamId}' for the 'cursor' value not found."); } - $queries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument); + $cursor->setValue($cursorDocument); } - $results = $dbForProject->find('teams', \array_merge($filterQueries, $queries)); + $filterQueries = Query::groupByType($queries)['filters']; + + $results = $dbForProject->find('teams', $queries); $total = $dbForProject->count('teams', $filterQueries, APP_LIMIT_COUNT); $response->dynamic(new Document([ @@ -464,15 +467,11 @@ App::get('/v1/teams/:teamId/memberships') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_MEMBERSHIP_LIST) ->param('teamId', '', new UID(), 'Team ID.') + ->param('queries', [], new Memberships(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Memberships::ALLOWED_ATTRIBUTES), 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), 'Maximum number of memberships to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) - ->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', 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. [learn more about pagination](https://appwrite.io/docs/pagination)', true) - ->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true) - ->param('orderType', Database::ORDER_ASC, new WhiteList([Database::ORDER_ASC, Database::ORDER_DESC], true), 'Order result by ' . Database::ORDER_ASC . ' or ' . Database::ORDER_DESC . ' order.', true) ->inject('response') ->inject('dbForProject') - ->action(function (string $teamId, string $search, int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject) { + ->action(function (string $teamId, array $queries, string $search, Response $response, Database $dbForProject) { $team = $dbForProject->getDocument('teams', $teamId); @@ -480,29 +479,34 @@ App::get('/v1/teams/:teamId/memberships') throw new Exception(Exception::TEAM_NOT_FOUND); } - $filterQueries = [Query::equal('teamId', [$teamId])]; + $queries = Query::parseQueries($queries); if (!empty($search)) { - $filterQueries[] = Query::search('search', $search); + $queries[] = Query::search('search', $search); } - $otherQueries = []; - $otherQueries[] = Query::limit($limit); - $otherQueries[] = Query::offset($offset); - $otherQueries[] = $orderType === Database::ORDER_ASC ? Query::orderAsc('') : Query::orderDesc(''); - if (!empty($cursor)) { - $cursorDocument = $dbForProject->getDocument('memberships', $cursor); + // Set internal queries + $queries[] = Query::equal('teamId', [$teamId]); + + // Get cursor document if there was a cursor query + $cursor = reset(Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE)); + if ($cursor) { + /** @var Query $cursor */ + $membershipId = $cursor->getValue(); + $cursorDocument = $dbForProject->getDocument('memberships', $membershipId); if ($cursorDocument->isEmpty()) { - throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Membership '{$cursor}' for the 'cursor' value not found."); + throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Membership '{$membershipId}' for the 'cursor' value not found."); } - $otherQueries[] = $cursorDirection === Database::CURSOR_AFTER ? Query::cursorAfter($cursorDocument) : Query::cursorBefore($cursorDocument); + $cursor->setValue($cursorDocument); } + $filterQueries = Query::groupByType($queries)['filters']; + $memberships = $dbForProject->find( collection: 'memberships', - queries: \array_merge($filterQueries, $otherQueries), + queries: $queries, ); $total = $dbForProject->count( @@ -851,18 +855,12 @@ App::get('/v1/teams/:teamId/logs') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_LOG_LIST) ->param('teamId', null, new UID(), 'Team ID.') - ->param('limit', 25, new Range(0, 100), 'Maximum number of logs to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) - ->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true) + ->param('queries', [], new Queries(new Limit(), new Offset()), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Only supported methods are limit and offset', true) ->inject('response') ->inject('dbForProject') ->inject('locale') ->inject('geodb') - ->action(function ($teamId, $limit, $offset, $response, $dbForProject, $locale, $geodb) { - /** @var Appwrite\Utopia\Response $response */ - /** @var Utopia\Database\Document $project */ - /** @var Utopia\Database\Database $dbForProject */ - /** @var Utopia\Locale\Locale $locale */ - /** @var MaxMind\Db\Reader $geodb */ + ->action(function (string $teamId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) { $team = $dbForProject->getDocument('teams', $teamId); @@ -870,6 +868,11 @@ App::get('/v1/teams/:teamId/logs') throw new Exception(Exception::TEAM_NOT_FOUND); } + $queries = Query::parseQueries($queries); + $grouped = Query::groupByType($queries); + $limit = $grouped['limit'] ?? APP_LIMIT_COUNT; + $offset = $grouped['offset'] ?? 0; + $audit = new Audit($dbForProject); $resource = 'team/' . $team->getId(); $logs = $audit->getLogsByResource($resource, $limit, $offset); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index a6d4c57f73..04af50ceb0 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -355,12 +355,9 @@ App::get('/v1/users') $queries[] = Query::search('search', $search); } - // Set default limit - $queries[] = Query::limit(25); - // Get cursor document if there was a cursor query - $cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE)[0] ?? null; - if ($cursor !== null) { + $cursor = reset(Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE)); + if ($cursor) { /** @var Query $cursor */ $userId = $cursor->getValue(); $cursorDocument = $dbForProject->getDocument('users', $userId); @@ -544,7 +541,7 @@ App::get('/v1/users/:userId/logs') $queries = Query::parseQueries($queries); $grouped = Query::groupByType($queries); - $limit = $grouped['limit'] ?? 25; + $limit = $grouped['limit'] ?? APP_LIMIT_COUNT; $offset = $grouped['offset'] ?? 0; $audit = new Audit($dbForProject); diff --git a/docs/references/databases/get-logs.md b/docs/references/databases/get-logs.md new file mode 100644 index 0000000000..8e49da4603 --- /dev/null +++ b/docs/references/databases/get-logs.md @@ -0,0 +1 @@ +Get the database activity logs list by its unique ID. \ No newline at end of file diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Collection.php b/src/Appwrite/Utopia/Database/Validator/Queries/Base.php similarity index 73% rename from src/Appwrite/Utopia/Database/Validator/Queries/Collection.php rename to src/Appwrite/Utopia/Database/Validator/Queries/Base.php index 0c29ecd2ff..89c82c8e82 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Collection.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Base.php @@ -2,7 +2,7 @@ namespace Appwrite\Utopia\Database\Validator\Queries; -use Appwrite\Utopia\Database\Validator\IndexedQueries; +use Appwrite\Utopia\Database\Validator\Queries; use Appwrite\Utopia\Database\Validator\Query\Limit; use Appwrite\Utopia\Database\Validator\Query\Offset; use Appwrite\Utopia\Database\Validator\Query\Cursor; @@ -12,7 +12,7 @@ use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\Document; -class Collection extends IndexedQueries +class Base extends Queries { /** * Expression constructor @@ -49,30 +49,16 @@ class Collection extends IndexedQueries 'array' => false, ]); $attributes[] = new Document([ - '$id' => '$createdAt', + 'key' => '$createdAt', 'type' => Database::VAR_DATETIME, 'array' => false, ]); $attributes[] = new Document([ - '$id' => '$updatedAt', + 'key' => '$updatedAt', 'type' => Database::VAR_DATETIME, 'array' => false, ]); - $indexes = []; - foreach ($allowedAttributes as $attribute) { - $indexes[] = new Document([ - 'status' => 'available', - 'type' => Database::INDEX_KEY, - 'attributes' => [$attribute] - ]); - } - $indexes[] = new Document([ - 'status' => 'available', - 'type' => Database::INDEX_FULLTEXT, - 'attributes' => ['search'] - ]); - $validators = [ new Limit(), new Offset(), @@ -81,6 +67,6 @@ class Collection extends IndexedQueries new Order($attributes), ]; - parent::__construct($attributes, $indexes, ...$validators); + parent::__construct(...$validators); } } diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Buckets.php b/src/Appwrite/Utopia/Database/Validator/Queries/Buckets.php new file mode 100644 index 0000000000..c4d187520f --- /dev/null +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Buckets.php @@ -0,0 +1,24 @@ + '$id', + 'type' => Database::VAR_STRING, + 'array' => false, + ]); + $attributes[] = new Document([ + 'key' => '$createdAt', + 'type' => Database::VAR_DATETIME, + 'array' => false, + ]); + $attributes[] = new Document([ + 'key' => '$updatedAt', + 'type' => Database::VAR_DATETIME, + 'array' => false, + ]); + + $validators = [ + new Limit(), + new Offset(), + new Cursor(), + new Filter($attributes), + new Order($attributes), + ]; + + parent::__construct($attributes, $indexes, ...$validators); + } +} diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Executions.php b/src/Appwrite/Utopia/Database/Validator/Queries/Executions.php new file mode 100644 index 0000000000..74996936b9 --- /dev/null +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Executions.php @@ -0,0 +1,22 @@ + $this->getProject()['$id'], 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, ]), [ - 'limit' => 1 + 'queries' => [ 'limit(1)' ], ]); $this->assertEquals($responseLimit['headers']['status-code'], 200); @@ -408,7 +408,7 @@ trait AccountBase 'x-appwrite-project' => $this->getProject()['$id'], 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, ]), [ - 'offset' => 1 + 'queries' => [ 'offset(1)' ], ]); $this->assertEquals($responseOffset['headers']['status-code'], 200); @@ -425,8 +425,7 @@ trait AccountBase 'x-appwrite-project' => $this->getProject()['$id'], 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, ]), [ - 'limit' => 1, - 'offset' => 1 + 'queries' => [ 'limit(1)', 'offset(1)' ], ]); $this->assertEquals($responseLimitOffset['headers']['status-code'], 200); diff --git a/tests/e2e/Services/Databases/DatabasesBase.php b/tests/e2e/Services/Databases/DatabasesBase.php index b28127fdb6..114a80dd3b 100644 --- a/tests/e2e/Services/Databases/DatabasesBase.php +++ b/tests/e2e/Services/Databases/DatabasesBase.php @@ -985,8 +985,7 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'orderAttributes' => ['releaseYear'], - 'orderTypes' => ['ASC'], + 'queries' => [ 'orderAsc("releaseYear")' ], ]); $this->assertEquals(200, $documents['headers']['status-code']); @@ -1006,8 +1005,7 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'orderAttributes' => ['releaseYear'], - 'orderTypes' => ['DESC'], + 'queries' => [ 'orderDesc("releaseYear")' ], ]); $this->assertEquals(200, $documents['headers']['status-code']); @@ -1122,7 +1120,7 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'cursor' => $base['body']['documents'][0]['$id'] + 'queries' => [ 'cursorAfter("' . $base['body']['documents'][0]['$id'] . '")' ], ]); $this->assertEquals(200, $documents['headers']['status-code']); @@ -1134,7 +1132,7 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'cursor' => $base['body']['documents'][2]['$id'] + 'queries' => [ 'cursorAfter("' . $base['body']['documents'][2]['$id'] . '")' ], ]); $this->assertEquals(200, $documents['headers']['status-code']); @@ -1147,8 +1145,7 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'orderAttributes' => ['releaseYear'], - 'orderTypes' => ['ASC'], + 'queries' => [ 'orderAsc("releaseYear")' ], ]); $this->assertEquals(200, $base['headers']['status-code']); @@ -1161,9 +1158,7 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'orderAttributes' => ['releaseYear'], - 'orderTypes' => ['ASC'], - 'cursor' => $base['body']['documents'][1]['$id'] + 'queries' => [ 'cursorAfter("' . $base['body']['documents'][1]['$id'] . '")', 'orderAsc("releaseYear")' ], ]); $this->assertEquals(200, $documents['headers']['status-code']); @@ -1177,8 +1172,7 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'orderAttributes' => ['releaseYear'], - 'orderTypes' => ['DESC'], + 'queries' => [ 'orderDesc("releaseYear")' ], ]); $this->assertEquals(200, $base['headers']['status-code']); @@ -1191,9 +1185,7 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'orderAttributes' => ['releaseYear'], - 'orderTypes' => ['DESC'], - 'cursor' => $base['body']['documents'][1]['$id'] + 'queries' => [ 'cursorAfter("' . $base['body']['documents'][1]['$id'] . '")', 'orderDesc("releaseYear")' ], ]); $this->assertEquals(200, $documents['headers']['status-code']); @@ -1207,7 +1199,7 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'cursor' => 'unknown' + 'queries' => [ 'cursorAfter("unknown")' ], ]); $this->assertEquals(400, $documents['headers']['status-code']); @@ -1239,8 +1231,7 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'cursor' => $base['body']['documents'][2]['$id'], - 'cursorDirection' => Database::CURSOR_BEFORE + 'queries' => [ 'cursorBefore("' . $base['body']['documents'][2]['$id'] . '")' ], ]); $this->assertEquals(200, $documents['headers']['status-code']); @@ -1252,8 +1243,7 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'cursor' => $base['body']['documents'][0]['$id'], - 'cursorDirection' => Database::CURSOR_BEFORE + 'queries' => [ 'cursorBefore("' . $base['body']['documents'][0]['$id'] . '")' ], ]); $this->assertEquals(200, $documents['headers']['status-code']); @@ -1266,8 +1256,7 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'orderAttributes' => ['releaseYear'], - 'orderTypes' => ['ASC'], + 'queries' => [ 'orderAsc("releaseYear")' ], ]); $this->assertEquals(200, $base['headers']['status-code']); @@ -1280,10 +1269,7 @@ trait DatabasesBase '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 + 'queries' => [ 'cursorBefore("' . $base['body']['documents'][1]['$id'] . '")', 'orderAsc("releaseYear")' ], ]); $this->assertEquals(200, $documents['headers']['status-code']); @@ -1297,8 +1283,7 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'orderAttributes' => ['releaseYear'], - 'orderTypes' => ['DESC'], + 'queries' => [ 'orderDesc("releaseYear")' ], ]); $this->assertEquals(200, $base['headers']['status-code']); @@ -1311,10 +1296,7 @@ trait DatabasesBase '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 + 'queries' => [ 'cursorBefore("' . $base['body']['documents'][1]['$id'] . '")', 'orderDesc("releaseYear")' ], ]); $this->assertEquals(200, $documents['headers']['status-code']); @@ -1334,9 +1316,7 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'limit' => 1, - 'orderAttributes' => ['releaseYear'], - 'orderTypes' => ['ASC'], + 'queries' => [ 'limit(1)', 'orderAsc("releaseYear")' ], ]); $this->assertEquals(200, $documents['headers']['status-code']); @@ -1347,10 +1327,7 @@ trait DatabasesBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'limit' => 2, - 'offset' => 1, - 'orderAttributes' => ['releaseYear'], - 'orderTypes' => ['ASC'], + 'queries' => [ 'limit(2)', 'offset(1)', 'orderAsc("releaseYear")' ], ]); $this->assertEquals(200, $documents['headers']['status-code']); diff --git a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php index 40238550f7..bd4e8aa77b 100644 --- a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php +++ b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php @@ -50,15 +50,61 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals($test1['body']['$id'], $databases['body']['databases'][0]['$id']); $this->assertEquals($test2['body']['$id'], $databases['body']['databases'][1]['$id']); - /** - * Test for Order - */ $base = array_reverse($databases['body']['databases']); + $databases = $this->client->call(Client::METHOD_GET, '/databases', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'orderType' => 'DESC' + 'queries' => [ 'limit(1)' ], + ]); + $this->assertEquals(200, $databases['headers']['status-code']); + $this->assertCount(1, $databases['body']['databases']); + + $databases = $this->client->call(Client::METHOD_GET, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ 'offset(1)' ], + ]); + $this->assertEquals(200, $databases['headers']['status-code']); + $this->assertCount(1, $databases['body']['databases']); + + $databases = $this->client->call(Client::METHOD_GET, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ 'equal("name", ["Test 1", "Test 2"])' ], + ]); + $this->assertEquals(200, $databases['headers']['status-code']); + $this->assertCount(2, $databases['body']['databases']); + + $databases = $this->client->call(Client::METHOD_GET, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ 'equal("name", "Test 2")' ], + ]); + $this->assertEquals(200, $databases['headers']['status-code']); + $this->assertCount(1, $databases['body']['databases']); + + $databases = $this->client->call(Client::METHOD_GET, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ 'equal("$id", "first")' ], + ]); + $this->assertEquals(200, $databases['headers']['status-code']); + $this->assertCount(1, $databases['body']['databases']); + + /** + * Test for Order + */ + $databases = $this->client->call(Client::METHOD_GET, '/databases', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ 'orderDesc("")' ], ]); $this->assertEquals(2, $databases['body']['total']); @@ -77,7 +123,7 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'cursor' => $base['body']['databases'][0]['$id'] + 'queries' => [ 'cursorAfter("' . $base['body']['databases'][0]['$id'] . '")' ], ]); $this->assertCount(1, $databases['body']['databases']); @@ -87,7 +133,7 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'cursor' => $base['body']['databases'][1]['$id'] + 'queries' => [ 'cursorAfter("' . $base['body']['databases'][1]['$id'] . '")' ], ]); $this->assertCount(0, $databases['body']['databases']); @@ -105,8 +151,7 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'cursor' => $base['body']['databases'][1]['$id'], - 'cursorDirection' => Database::CURSOR_BEFORE + 'queries' => [ 'cursorBefore("' . $base['body']['databases'][1]['$id'] . '")' ], ]); $this->assertCount(1, $databases['body']['databases']); @@ -116,8 +161,7 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'cursor' => $base['body']['databases'][0]['$id'], - 'cursorDirection' => Database::CURSOR_BEFORE + 'queries' => [ 'cursorBefore("' . $base['body']['databases'][0]['$id'] . '")' ], ]); $this->assertCount(0, $databases['body']['databases']); @@ -163,7 +207,7 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'cursor' => 'unknown', + 'queries' => [ 'cursorAfter("unknown")' ], ]); $this->assertEquals(400, $response['headers']['status-code']); @@ -287,15 +331,56 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals($test1['body']['$id'], $collections['body']['collections'][0]['$id']); $this->assertEquals($test2['body']['$id'], $collections['body']['collections'][1]['$id']); - /** - * Test for Order - */ $base = array_reverse($collections['body']['collections']); + $collections = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'orderType' => 'DESC' + 'queries' => [ 'limit(1)' ] + ]); + + $this->assertEquals(200, $collections['headers']['status-code']); + $this->assertCount(1, $collections['body']['collections']); + + $collections = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ 'offset(1)' ] + ]); + + $this->assertEquals(200, $collections['headers']['status-code']); + $this->assertCount(1, $collections['body']['collections']); + + $collections = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ 'equal("enabled", true)' ] + ]); + + $this->assertEquals(200, $collections['headers']['status-code']); + $this->assertCount(2, $collections['body']['collections']); + + $collections = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ 'equal("enabled", false)' ] + ]); + + $this->assertEquals(200, $collections['headers']['status-code']); + $this->assertCount(0, $collections['body']['collections']); + + /** + * Test for Order + */ + $collections = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ 'orderDesc("")' ], ]); $this->assertEquals(2, $collections['body']['total']); @@ -314,7 +399,7 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'cursor' => $base['body']['collections'][0]['$id'] + 'queries' => [ 'cursorAfter("' . $base['body']['collections'][0]['$id'] . '")' ], ]); $this->assertCount(1, $collections['body']['collections']); @@ -324,7 +409,7 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'cursor' => $base['body']['collections'][1]['$id'] + 'queries' => [ 'cursorAfter("' . $base['body']['collections'][1]['$id'] . '")' ], ]); $this->assertCount(0, $collections['body']['collections']); @@ -342,8 +427,7 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'cursor' => $base['body']['collections'][1]['$id'], - 'cursorDirection' => Database::CURSOR_BEFORE + 'queries' => [ 'cursorBefore("' . $base['body']['collections'][1]['$id'] . '")' ], ]); $this->assertCount(1, $collections['body']['collections']); @@ -353,8 +437,7 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'cursor' => $base['body']['collections'][0]['$id'], - 'cursorDirection' => Database::CURSOR_BEFORE + 'queries' => [ 'cursorBefore("' . $base['body']['collections'][0]['$id'] . '")' ], ]); $this->assertCount(0, $collections['body']['collections']); @@ -400,7 +483,7 @@ class DatabasesCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'cursor' => 'unknown', + 'queries' => [ 'cursorAfter("unknown")' ], ]); $this->assertEquals(400, $response['headers']['status-code']); diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index 52ebafc74d..3d243a5491 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -361,7 +361,51 @@ class FunctionsCustomClientTest extends Scope 'x-appwrite-project' => $projectId, 'x-appwrite-key' => $apikey, ], [ - 'cursor' => $base['body']['executions'][0]['$id'] + 'queries' => [ 'limit(1)' ] + ]); + + $this->assertEquals(200, $executions['headers']['status-code']); + $this->assertCount(1, $executions['body']['executions']); + + $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-key' => $apikey, + ], [ + 'queries' => [ 'offset(1)' ] + ]); + + $this->assertEquals(200, $executions['headers']['status-code']); + $this->assertCount(1, $executions['body']['executions']); + + $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-key' => $apikey, + ], [ + 'queries' => [ 'equal("status", ["completed"])' ] + ]); + + $this->assertEquals(200, $executions['headers']['status-code']); + $this->assertCount(2, $executions['body']['executions']); + + $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-key' => $apikey, + ], [ + 'queries' => [ 'equal("status", ["failed"])' ] + ]); + + $this->assertEquals(200, $executions['headers']['status-code']); + $this->assertCount(0, $executions['body']['executions']); + + $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-key' => $apikey, + ], [ + 'queries' => [ 'cursorAfter("' . $base['body']['executions'][0]['$id'] . '")' ], ]); $this->assertCount(1, $executions['body']['executions']); @@ -372,8 +416,7 @@ class FunctionsCustomClientTest extends Scope 'x-appwrite-project' => $projectId, 'x-appwrite-key' => $apikey, ], [ - 'cursor' => $base['body']['executions'][1]['$id'], - 'cursorDirection' => Database::CURSOR_BEFORE + 'queries' => [ 'cursorBefore("' . $base['body']['executions'][1]['$id'] . '")' ], ]); // Cleanup : Delete function diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 2678179e31..10a1f2064b 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -97,6 +97,46 @@ class FunctionsCustomServerTest extends Scope $this->assertCount(1, $response['body']['functions']); $this->assertEquals($response['body']['functions'][0]['name'], 'Test'); + $response = $this->client->call(Client::METHOD_GET, '/functions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ 'limit(0)' ] + ]); + + $this->assertEquals($response['headers']['status-code'], 200); + $this->assertCount(0, $response['body']['functions']); + + $response = $this->client->call(Client::METHOD_GET, '/functions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ 'offset(1)' ] + ]); + + $this->assertEquals($response['headers']['status-code'], 200); + $this->assertCount(0, $response['body']['functions']); + + $response = $this->client->call(Client::METHOD_GET, '/functions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ 'equal("status", "disabled")' ] + ]); + + $this->assertEquals($response['headers']['status-code'], 200); + $this->assertCount(1, $response['body']['functions']); + + $response = $this->client->call(Client::METHOD_GET, '/functions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ 'equal("status", "enabled")' ] + ]); + + $this->assertEquals($response['headers']['status-code'], 200); + $this->assertCount(0, $response['body']['functions']); + $response = $this->client->call(Client::METHOD_GET, '/functions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -159,7 +199,7 @@ class FunctionsCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'cursor' => $functions['body']['functions'][0]['$id'] + 'queries' => [ 'cursorAfter("' . $functions['body']['functions'][0]['$id'] . '")' ], ]); $this->assertEquals($response['headers']['status-code'], 200); @@ -170,8 +210,7 @@ class FunctionsCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'cursor' => $functions['body']['functions'][1]['$id'], - 'cursorDirection' => Database::CURSOR_BEFORE + 'queries' => [ 'cursorBefore("' . $functions['body']['functions'][1]['$id'] . '")' ], ]); $this->assertEquals($response['headers']['status-code'], 200); @@ -185,7 +224,7 @@ class FunctionsCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'cursor' => 'unknown', + 'queries' => [ 'cursorAfter("unknown")' ], ]); $this->assertEquals($response['headers']['status-code'], 400); @@ -413,6 +452,46 @@ class FunctionsCustomServerTest extends Scope $this->assertCount(2, $function['body']['deployments']); $this->assertEquals($function['body']['deployments'][0]['$id'], $data['deploymentId']); + $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ 'limit(1)' ] + ]); + + $this->assertEquals($function['headers']['status-code'], 200); + $this->assertCount(1, $function['body']['deployments']); + + $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ 'offset(1)' ] + ]); + + $this->assertEquals($function['headers']['status-code'], 200); + $this->assertCount(1, $function['body']['deployments']); + + $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ 'equal("entrypoint", "index.php")' ] + ]); + + $this->assertEquals($function['headers']['status-code'], 200); + $this->assertCount(2, $function['body']['deployments']); + + $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ 'equal("entrypoint", "index.js")' ] + ]); + + $this->assertEquals($function['headers']['status-code'], 200); + $this->assertCount(0, $function['body']['deployments']); + $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -549,6 +628,36 @@ class FunctionsCustomServerTest extends Scope $this->assertCount(1, $function['body']['executions']); $this->assertEquals($function['body']['executions'][0]['$id'], $data['executionId']); + $response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/executions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ 'limit(0)' ] + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertCount(0, $response['body']['executions']); + + $response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/executions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ 'offset(1)' ] + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertCount(0, $response['body']['executions']); + + $response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/executions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ 'equal("trigger", "http")' ] + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertCount(1, $response['body']['executions']); + /** * Test search queries */ diff --git a/tests/e2e/Services/Storage/StorageBase.php b/tests/e2e/Services/Storage/StorageBase.php index 3e695c73b1..026979db12 100644 --- a/tests/e2e/Services/Storage/StorageBase.php +++ b/tests/e2e/Services/Storage/StorageBase.php @@ -259,6 +259,42 @@ trait StorageBase $this->assertGreaterThan(0, $files['body']['total']); $this->assertGreaterThan(0, count($files['body']['files'])); + $files = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $data['bucketId'] . '/files', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ 'limit(0)' ] + ]); + $this->assertEquals(200, $files['headers']['status-code']); + $this->assertEquals(0, count($files['body']['files'])); + + $files = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $data['bucketId'] . '/files', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ 'offset(1)' ] + ]); + $this->assertEquals(200, $files['headers']['status-code']); + $this->assertEquals(0, count($files['body']['files'])); + + $files = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $data['bucketId'] . '/files', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ 'equal("mimeType", "image/png")' ] + ]); + $this->assertEquals(200, $files['headers']['status-code']); + $this->assertEquals(1, count($files['body']['files'])); + + $files = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $data['bucketId'] . '/files', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ 'equal("mimeType", "image/jpeg")' ] + ]); + $this->assertEquals(200, $files['headers']['status-code']); + $this->assertEquals(0, count($files['body']['files'])); + /** * Test for FAILURE unknown Bucket */ diff --git a/tests/e2e/Services/Storage/StorageCustomServerTest.php b/tests/e2e/Services/Storage/StorageCustomServerTest.php index 13130f1ea1..ac03f2b83c 100644 --- a/tests/e2e/Services/Storage/StorageCustomServerTest.php +++ b/tests/e2e/Services/Storage/StorageCustomServerTest.php @@ -98,7 +98,47 @@ class StorageCustomServerTest extends Scope 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'cursor' => $response['body']['buckets'][0]['$id'], + 'queries' => [ 'limit(1)' ], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertCount(1, $response['body']['buckets']); + + $response = $this->client->call(Client::METHOD_GET, '/storage/buckets', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ 'offset(1)' ], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertCount(1, $response['body']['buckets']); + + $response = $this->client->call(Client::METHOD_GET, '/storage/buckets', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ 'equal("$id", "bucket1")' ], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertCount(1, $response['body']['buckets']); + + $response = $this->client->call(Client::METHOD_GET, '/storage/buckets', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ 'equal("fileSecurity", true)' ], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertCount(2, $response['body']['buckets']); + + $response = $this->client->call(Client::METHOD_GET, '/storage/buckets', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ 'cursorAfter("' . $response['body']['buckets'][0]['$id'] . '")' ], ]); $this->assertEquals(200, $response['headers']['status-code']); diff --git a/tests/e2e/Services/Teams/TeamsBase.php b/tests/e2e/Services/Teams/TeamsBase.php index addfcfa631..854a041503 100644 --- a/tests/e2e/Services/Teams/TeamsBase.php +++ b/tests/e2e/Services/Teams/TeamsBase.php @@ -129,25 +129,41 @@ trait TeamsBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'limit' => 2, + 'queries' => [ 'limit(2)' ], ]); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertGreaterThan(0, $response['body']['total']); - $this->assertIsInt($response['body']['total']); - $this->assertCount(2, $response['body']['teams']); + $this->assertEquals(2, count($response['body']['teams'])); $response = $this->client->call(Client::METHOD_GET, '/teams', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'offset' => 1, + 'queries' => [ 'offset(1)' ], ]); $this->assertEquals(200, $response['headers']['status-code']); - $this->assertGreaterThan(0, $response['body']['total']); - $this->assertIsInt($response['body']['total']); - $this->assertGreaterThan(2, $response['body']['teams']); + $this->assertGreaterThan(1, count($response['body']['teams'])); + + $response = $this->client->call(Client::METHOD_GET, '/teams', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ 'greaterThanEqual("total", 0)' ], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertGreaterThan(2, count($response['body']['teams'])); + + $response = $this->client->call(Client::METHOD_GET, '/teams', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ 'equal("name", ["Arsenal", "Newcastle"])' ], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(2, count($response['body']['teams'])); $response = $this->client->call(Client::METHOD_GET, '/teams', array_merge([ 'content-type' => 'application/json', @@ -192,7 +208,7 @@ trait TeamsBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'limit' => 2, + 'queries' => [ 'limit(2)' ], ]); $this->assertEquals(200, $teams['headers']['status-code']); @@ -204,8 +220,7 @@ trait TeamsBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'limit' => 1, - 'cursor' => $teams['body']['teams'][0]['$id'] + 'queries' => [ 'limit(1)', 'cursorAfter("' . $teams['body']['teams'][0]['$id'] . '")' ], ]); $this->assertEquals(200, $response['headers']['status-code']); @@ -218,9 +233,7 @@ trait TeamsBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'limit' => 1, - 'cursor' => $teams['body']['teams'][1]['$id'], - 'cursorDirection' => Database::CURSOR_BEFORE + 'queries' => [ 'limit(1)', 'cursorBefore("' . $teams['body']['teams'][1]['$id'] . '")' ], ]); $this->assertEquals(200, $response['headers']['status-code']); @@ -236,7 +249,7 @@ trait TeamsBase 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'cursor' => 'unknown' + 'queries' => [ 'cursorAfter("unknown")' ], ]); $this->assertEquals(400, $response['headers']['status-code']); diff --git a/tests/e2e/Services/Teams/TeamsBaseClient.php b/tests/e2e/Services/Teams/TeamsBaseClient.php index 5fe5c370a2..266bdefc8d 100644 --- a/tests/e2e/Services/Teams/TeamsBaseClient.php +++ b/tests/e2e/Services/Teams/TeamsBaseClient.php @@ -35,6 +35,46 @@ trait TeamsBaseClient $membershipId = $response['body']['memberships'][0]['$id']; + $response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ 'limit(0)' ] + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertCount(0, $response['body']['memberships']); + + $response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ 'offset(1)' ] + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertCount(0, $response['body']['memberships']); + + $response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ 'equal("confirm", true)' ] + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertCount(1, $response['body']['memberships']); + + $response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ 'equal("confirm", false)' ] + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertCount(0, $response['body']['memberships']); + $response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -212,7 +252,7 @@ trait TeamsBaseClient 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'cursor' => $memberships['body']['memberships'][0]['$id'] + 'queries' => [ 'cursorAfter("' . $memberships['body']['memberships'][0]['$id'] . '")' ] ]); $this->assertEquals(200, $response['headers']['status-code']); diff --git a/tests/unit/Utopia/Database/Validator/Queries/CollectionTest.php b/tests/unit/Utopia/Database/Validator/Queries/CollectionTest.php index 198fc2895d..3899ce0d80 100644 --- a/tests/unit/Utopia/Database/Validator/Queries/CollectionTest.php +++ b/tests/unit/Utopia/Database/Validator/Queries/CollectionTest.php @@ -2,7 +2,7 @@ namespace Tests\Unit\Utopia\Database\Validator\Queries; -use Appwrite\Utopia\Database\Validator\Queries\Collection; +use Appwrite\Utopia\Database\Validator\Queries\Base; use PHPUnit\Framework\TestCase; class CollectionTest extends TestCase @@ -17,27 +17,25 @@ class CollectionTest extends TestCase public function testEmptyQueries(): void { - $validator = new Collection('users', []); + $validator = new Base('users', []); $this->assertEquals($validator->isValid([]), true); } public function testValid(): void { - $validator = new Collection('users', ['name', 'search']); + $validator = new Base('users', ['name', 'search']); $this->assertEquals(true, $validator->isValid(['cursorAfter("asdf")']), $validator->getDescription()); $this->assertEquals(true, $validator->isValid(['equal("name", "value")']), $validator->getDescription()); $this->assertEquals(true, $validator->isValid(['limit(10)']), $validator->getDescription()); $this->assertEquals(true, $validator->isValid(['offset(10)']), $validator->getDescription()); $this->assertEquals(true, $validator->isValid(['orderAsc("name")']), $validator->getDescription()); - $this->assertEquals(true, $validator->isValid(['search("search", "value")']), $validator->getDescription()); } public function testMissingIndex(): void { - $validator = new Collection('users', ['name']); + $validator = new Base('users', ['name']); $this->assertEquals(false, $validator->isValid(['equal("dne", "value")']), $validator->getDescription()); $this->assertEquals(false, $validator->isValid(['orderAsc("dne")']), $validator->getDescription()); - $this->assertEquals(false, $validator->isValid(['search("search", "value")']), $validator->getDescription()); } } diff --git a/tests/unit/Utopia/Database/Validator/Queries/UsersTest.php b/tests/unit/Utopia/Database/Validator/Queries/UsersTest.php index 24a818c128..122d83d885 100644 --- a/tests/unit/Utopia/Database/Validator/Queries/UsersTest.php +++ b/tests/unit/Utopia/Database/Validator/Queries/UsersTest.php @@ -30,7 +30,6 @@ class UsersTest extends TestCase $this->assertEquals(true, $validator->isValid(['greaterThan("registration", "2020-10-15 06:38")']), $validator->getDescription()); $this->assertEquals(true, $validator->isValid(['equal("emailVerification", true)']), $validator->getDescription()); $this->assertEquals(true, $validator->isValid(['equal("phoneVerification", true)']), $validator->getDescription()); - $this->assertEquals(true, $validator->isValid(['search("search", "value")']), $validator->getDescription()); /** * Test for Failure